天天看点

Python快速编程技巧

参与文末每日话题讨论,赠送异步新书

 异步图书君

而在本文中,我们将看到更多特殊的例子,以便让你更加熟悉Scrapy的两个最重要的类——<code>Request</code>和<code>Response</code>。

1.1 需要登录的爬虫

通常情况下,你会发现自己想要抽取数据的网站存在登录机制。大部分情况下,网站会要求你提供用户名和密码用于登录。你可以从<code>http://web:9312/dynamic</code>(从dev机器访问)或<code>http://localhost:9312/ dynamic</code>(从宿主机浏览器访问)找到我们要使用的例子。如果使用"user"作为用户名,"pass"作为密码的话,你就可以访问到包含3个房产页面链接的网页。不过现在的问题是,要如何使用Scrapy执行相同的操作?

让我们使用Google Chrome浏览器的开发者工具来尝试理解登录的工作过程(见图1.1)。首先,打开Network选项卡(1)。然后,填写用户名和密码,并单击Login(2)。如果用户名和密码正确,你将会看到包含3个链接的页面。如果用户名和密码不匹配,将会看到一个错误页。

Python快速编程技巧

图1.1 登录网站时的请求和响应

当按下Login按钮时,会在Google Chrome浏览器开发者工具的Network选项卡中看到一个包含Request Method: POST的请求,其目的地址为<code>http://localhost:9312/dynamic/login</code>。

当你单击该请求时(3),可以看到发送给服务端的数据,包括Form Data(4),其中包含了我们输入的用户名和密码。这些数据都是以文本形式传输给服务端的。Chrome浏览器只是将其组织起来,向我们更好地显示这些数据。服务端的响应是302 Found(5),使我们跳转到一个新的页面:<code>/dynamic/gated</code>。该页面只有在登录成功后才会出现。如果尝试直接访问<code>http://localhost:9312/dynamic/gated</code>,而不输入正确的用户名和密码的话,服务端会发现你在作弊,并跳转到错误页,其地址是<code>http:// localhost:9312/dynamic/error</code>。服务端是如何知道你和你的密码的呢?如果你单击开发者工具左侧的gated(6),就会发现在Request Headers区域下面(7)设置了一个Cookie值(8)。

总之,即使是一个单一的操作,比如登录,也可能涉及包括POST请求和HTTP跳转的多次服务端往返。Scrapy能够自动处理大部分操作,而我们需要编写的代码也很简单。

我们从第3章中名为<code>easy</code>的爬虫开始,创建一个新的爬虫,命名为<code>login</code>,保留原有文件,并修改爬虫中的<code>name</code>属性(如下所示):

classLoginSpider(CrawlSpider):  name = 'login'

我们需要通过执行到<code>http://localhost:9312/dynamic/login</code>的POST请求,发送登录的初始请求。这将通过Scrapy的<code>FormRequest</code>类实现该功能。要想使用该类,首先需要引入如下模块。

from scrapy.httpimport FormRequest

然后,将<code>start_urls</code>语句替换为<code>start_requests()</code>方法。这样做是因为在本例中,我们需要从一些更加定制化的请求开始,而不仅仅是几个URL。更确切地说就是,我们从该函数中创建并返回一个<code>FormRequest</code>。

# Start with a login requestdefstart_requests(self): return [  FormRequest(   "http://web:9312/dynamic/login",   formdata={"user":"user","pass": "pass"}     )]

虽然听起来不可思议,但是<code>CrawlSpider</code>(<code>LoginSpider</code>的基类)默认的<code>parse()</code>方法确实处理了<code>Response</code>,并且仍然能够使用第3章中的<code>Rule</code>和<code>LinkExtractor</code>。我们只编写了非常少的额外代码,这是因为Scrapy为我们透明处理了Cookie,并且一旦我们登录成功,就会在后续的请求中传输这些Cookie,就和浏览器执行的方式一样。接下来可以像平常一样,使用<code>scrapy crwal</code>运行。

&lt;strong&gt;$ scrapy crawl login&lt;/strong&gt;&lt;strong&gt;INFO: Scrapy 1.0.3 started (bot: properties)&lt;/strong&gt;&lt;strong&gt;...&lt;/strong&gt;&lt;strong&gt;DEBUG: Redirecting (302) to &amp;lt;GET .../gated&gt; from &amp;lt;POST .../login &gt;&lt;/strong&gt;&lt;strong&gt;DEBUG: Crawled (200) &amp;lt;GET .../data.php&gt;&lt;/strong&gt;&lt;strong&gt;DEBUG: Crawled (200) &amp;lt;GET .../property_000001.html&gt; (referer: .../data.&lt;/strong&gt;&lt;strong&gt;php)&lt;/strong&gt;&lt;strong&gt;DEBUG: Scraped from &amp;lt;200 .../property_000001.html&gt;&lt;/strong&gt;&lt;strong&gt;{'address': [u'Plaistow, London'],&lt;/strong&gt;&lt;strong&gt; 'date': [datetime.datetime(2015, 11, 25, 12, 7, 27, 120119)],&lt;/strong&gt;&lt;strong&gt; 'description': [u'features'],&lt;/strong&gt;&lt;strong&gt; 'image_urls': [u'http://web:9312/images/i02.jpg'],&lt;/strong&gt;&lt;strong&gt;...&lt;/strong&gt;&lt;strong&gt;INFO: Closing spider (finished)&lt;/strong&gt;&lt;strong&gt;INFO: Dumping Scrapy stats:&lt;/strong&gt;&lt;strong&gt; {...&lt;/strong&gt;&lt;strong&gt;  'downloader/request_method_count/GET': 4,&lt;/strong&gt;&lt;strong&gt;  'downloader/request_method_count/POST': 1,&lt;/strong&gt;&lt;strong&gt;...&lt;/strong&gt;&lt;strong&gt;  'item_scraped_count': 3,&lt;/strong&gt;

我们可以在日志中看到从<code>dynamic/login</code>到<code>dynamic/gated</code>的跳转,然后就会像平时那样抓取Item了。在统计中,可以看到1个POST请求和4个GET请求(一个是前往<code>dynamic/gated</code>索引页,另外3个是房产页面)。

如果使用了错误的用户名和密码,将会跳转到一个没有任何项目的页面,并且此时爬取过程会被终止,如下面的执行情况所示。

&lt;strong&gt;$ scrapy crawl login&lt;/strong&gt;&lt;strong&gt;INFO: Scrapy 1.0.3 started (bot: properties)&lt;/strong&gt;&lt;strong&gt;...&lt;/strong&gt;&lt;strong&gt;DEBUG: Redirecting (302) to &amp;lt;GET .../dynamic/error &gt; from &amp;lt;POST .../&lt;/strong&gt;&lt;strong&gt;dynamic/login&gt;&lt;/strong&gt;&lt;strong&gt;DEBUG: Crawled (200) &amp;lt;GET .../dynamic/error&gt;&lt;/strong&gt;&lt;strong&gt;...&lt;/strong&gt;&lt;strong&gt;INFO: Spider closed (closespider_itemcount)&lt;/strong&gt;

这是一个简单的登录示例,用于演示基本的登录机制。大多数网站都会拥有一些更加复杂的机制,不过Scrapy也都能够轻松处理。比如,一些网站要求你在执行POST请求时,将表单页中的某些表单变量传输到登录页,以便确认Cookie是启用的,同样也会让你在尝试暴力破解成千上万次用户名/密码的组合时更加困难。图1.2所示即为此种情况的一个示例。

Python快速编程技巧

图1.2 使用一次性随机数的一个更加高级的登录示例的请求和响应情况

比如,当访问<code>http://localhost:9312/dynamic/nonce</code>时,你会看到一个看起来一样的页面,但是当使用Chrome浏览器的开发者工具查看时,会发现页面的表单中有一个叫作nonce的隐藏字段。当提交该表单时(提交到<code>http://localhost:9312/ dynamic/nonce-login</code>),除非你既传输了正确的用户名/密码,又提交了服务端在你访问该登录页时给你的<code>nonce</code>值,否则登录不会成功。你无法猜测该值,因为它通常是随机且一次性的。这就表示要想成功登录,现在就需要请求两次了。你必须先访问表单页,然后再访问登录页传输数据。当然,Scrapy同样拥有内置函数可以帮助我们实现这一目的。

我们创建了一个和之前相似的<code>NonceLoginSpider</code>爬虫。现在,在<code>start_requests()</code>中,将返回一个简单的<code>Request</code>(不要忘记引入该模块)到表单页面中,并通过设置其<code>callback</code>属性为处理方法<code>parse_welcome()</code>手动处理响应。在<code>parse_welcome()</code>中,使用了<code>FormRequest</code>对象的辅助方法<code>from_response()</code>,以创建从原始表单中预填充所有字段和值的<code>FormRequest</code>对象。<code>FormRequest.from_response()</code>粗略模拟了一次在页面的第一个表单上的提交单击,此时所有字段留空。

该方法对于我们来说非常有用,因为它能够毫不费力地原样包含表单中的所有隐藏字段。我们所需要做的就是使用<code>formdata</code>参数填充<code>user</code>和<code>pass</code>字段以及返回<code>FormRequest</code>。下面是其相关代码。

# Start on the welcome pagedefstart_requests(self):  return [    Request(      "http://web:9312/dynamic/nonce",      callback=self.parse_welcome)  ]# Post welcome page's first form with the given user/passdefparse_welcome(self, response):  return FormRequest.from_response(    response,    formdata={"user":"user","pass": "pass"}  )

我们可以像平时一样运行爬虫。$ scrapy crawl noncelogin&lt;/strong&gt;

&lt;strong&gt;INFO: Scrapy 1.0.3 started (bot: properties)&lt;/strong&gt;&lt;strong&gt;...&lt;/strong&gt;&lt;strong&gt;DEBUG: Crawled (200) &amp;lt;GET .../dynamic/nonce&gt;&lt;/strong&gt;&lt;strong&gt;DEBUG: Redirecting (302) to &amp;lt;GET .../dynamic/gated &gt; from &amp;lt;POST .../&lt;/strong&gt;&lt;strong&gt;dynamic/login-nonce&gt;&lt;/strong&gt;&lt;strong&gt;DEBUG: Crawled (200) &amp;lt;GET .../dynamic/gated&gt;&lt;/strong&gt;&lt;strong&gt;...&lt;/strong&gt;&lt;strong&gt;INFO: Dumping Scrapy stats:&lt;/strong&gt;&lt;strong&gt; {...&lt;/strong&gt;&lt;strong&gt;  'downloader/request_method_count/GET': 5,&lt;/strong&gt;&lt;strong&gt;  'downloader/request_method_count/POST': 1,&lt;/strong&gt;&lt;strong&gt;...&lt;/strong&gt;&lt;strong&gt;  'item_scraped_count': 3,&lt;/strong&gt;

可以看到,第一个GET请求前往<code>/dynamic/nonce</code>页面,然后是POST请求,跳转到<code>/dynamic/nonce-login</code>页面,之后像前面的例子一样跳转到<code>/dynamic/gated</code>页面。关于登录的讨论就到这里。该示例使用两个步骤完成登录。只要你有足够的耐心,就可以形成任意长链,来执行几乎所有的登录操作。

有时,你会发现自己在页面寻找的数据无法从HTML页面中找到。比如,当访问<code>http://localhost:9312/static/</code>时(见图5.3),在页面任意位置右键单击inspect element(1, 2),可以看到其中包含所有常见HTML元素的DOM树。但是,当你使用<code>scrapy shell</code>请求,或是在Chrome浏览器中右键单击View Page Source(3, 4)时,则会发现该页面的HTML代码中并不包含关于房产的任何信息。那么,这些数据是从哪里来的呢?

图1.3 动态加载JSON对象时的页面请求与响应

与平常一样,遇到这类例子时,下一步操作应当是打开Chrome浏览器开发者工具的Network选项卡,来看看发生了什么。在左侧的列表中,可以看到加载本页面时Chrome执行的请求。在这个简单的页面中,只有3个请求:static/是刚才已经检查过的请求;jquery.min.js用于获取一个流行的Javascript框架的代码;而api.json看起来会让我们产生兴趣。当单击该请求(6),并单击右侧的Preview选项卡(7)时,就会发现这里面包含了我们正在寻找的数据。实际上,<code>http://localhost:9312/properties/api.json</code>包含了房产的ID和名称(8),如下所示。

[{  "id":0,  "title":"better set unique family well"},... {  "id":29,  "title":"better portered mile"}]

这是一个非常简单的JSON API的示例。更复杂的API可能需要你登录,使用POST请求,或返回更有趣的数据结构。无论在哪种情况下,JSON都是最简单的解析格式之一,因为你不需要编写任何XPath表达式就可以从中抽取出数据。

Python提供了一个非常好的JSON解析库。当我们执行<code>import json</code>时,就可以使用<code>json.loads(response.body)</code>解析JSON,将其转换为由Python原语、列表和字典组成的等效Python对象。

我们将第3章的<code>manual.py</code>拷贝过来,用于实现该功能。在本例中,这是最佳的起始选项,因为我们需要通过在JSON对象中找到的ID,手动创建房产URL以及<code>Request</code>对象。我们将该文件重命名为<code>api.py</code>,并将爬虫类重命名为<code>ApiSpider</code>,<code>name</code>属性修改为<code>api</code>。新的<code>start_urls</code>将会是JSON API的URL,如下所示。

start_urls = (  'http://web:9312/properties/api.json',)

如果你想执行POST请求,或是更复杂的操作,可以使用前一节中介绍的<code>start_requests()</code>方法。此时,Scrapy将会打开该URL,并调用包含以<code>Response</code>为参数的<code>parse()</code>方法。可以通过<code>import json</code>,使用如下代码解析JSON对象。

defparse(self, response):  base_url ="http://web:9312/properties/"  js = json.loads(response.body)  for iteminjs:    id = item["id"]    url = base_url +"property_%06d.html" % id    yield Request(url, callback=self.parse_item)

前面的代码使用了<code>json.loads(response.body)</code>,将<code>Response</code>这个JSON对象解析为Python列表,然后迭代该列表。对于列表中的每一项,我们将URL的3个部分(<code>base_url</code>、<code>property_%06d</code>以及<code>.html</code>)组合到一起。<code>base_url</code>是在前面定义的URL前缀。<code>%06d</code>是Python语法中非常有用的一部分,它可以让我们结合Python变量创建新的字符串。在本例中,<code>%06d</code>将会被变量<code>id</code>的值替换(本行结尾处%后面的变量)。<code>id</code>将会被视为数字(<code>%d</code>表示视为数字),并且如果不满6位,则会在前面加上0,扩展成6位字符。比如,<code>id</code>值为5,<code>%06d</code>将会被替换为000005,而如果<code>id</code>为34322,<code>%06d</code>则会被替换为034322。最终结果正是我们房产页面的有效URL。我们使用该URL形成一个新的<code>Request</code>对象,并像第3章一样使用<code>yield</code>。然后可以像平时那样使用<code>scrapy crawl</code>运行该示例。

&lt;strong&gt;$ scrapy crawl api&lt;/strong&gt;&lt;strong&gt;INFO: Scrapy 1.0.3 started (bot: properties)&lt;/strong&gt;&lt;strong&gt;...&lt;/strong&gt;&lt;strong&gt;DEBUG: Crawled (200) &amp;lt;GET ...properties/api.json&gt;&lt;/strong&gt;&lt;strong&gt;DEBUG: Crawled (200) &amp;lt;GET .../property_000029.html&gt;&lt;/strong&gt;&lt;strong&gt;...&lt;/strong&gt;&lt;strong&gt;INFO: Closing spider (finished)&lt;/strong&gt;&lt;strong&gt;INFO: Dumping Scrapy stats:&lt;/strong&gt;&lt;strong&gt;...&lt;/strong&gt;&lt;strong&gt;  'downloader/request_count': 31, ...&lt;/strong&gt;&lt;strong&gt;  'item_scraped_count': 30,&lt;/strong&gt;

你可能会注意到结尾处的状态是31个请求——每个Item一个请求,以及最初的<code>api.json</code>的请求。

很多情况下,在JSON API中会有感兴趣的信息,你可能想要将它们存储到<code>Item</code>中。在我们的示例中,为了演示这种情况,JSON API会在给定房产信息的标题前面加上"better"。比如,房产标题是"Covent Garden",API就会将标题写为"Better Covent Garden"。假设我们想要将这些"better"开头的标题存储到<code>Items</code>中,要如何将信息从<code>parse()</code>方法传递到<code>parse_item()</code>方法呢?

不要感到惊讶,通过在<code>parse()</code>生成的<code>Request</code>中设置一些东西,就能实现该功能。之后,可以从<code>parse_item()</code>接收到的<code>Response</code>中取得这些信息。<code>Request</code>有一个名为<code>meta</code>的字典,能够直接访问<code>Response</code>。比如在我们的例子中,可以在该字典中设置标题值,以存储来自JSON对象的标题。

title = item["title"]yield Request(url, meta={"title": title},callback=self.parse_item)

在<code>parse_item()</code>内部,可以使用该值替代之前使用过的XPath表达式。

l.add_value('title', response.meta['title'],       MapCompose(unicode.strip, unicode.title))

你会发现我们不再调用<code>add_xpath()</code>,而是转为调用<code>add_value()</code>,这是因为我们在该字段中将不会再使用到任何XPath表达式。现在,可以使用<code>scrapy crawl</code>运行这个新的爬虫,并且可以在<code>PropertyItems</code>中看到来自<code>api.json</code>的标题。

有这样一种趋势,当你开始使用一个框架时,做任何事情都可能会使用最复杂的方式。你在使用Scrapy时也会发现自己在做这样的事情。在疯狂于XPath等技术之前,值得停下来想一想:我选择的方式是从网站中抽取数据最简单的方式吗?

如果你能从索引页中抽取出基本相同的信息,就可以避免抓取每个房源页,从而得到数量级的提升。

比如,在房产示例中,我们所需要的所有信息都存在于索引页中,包括标题、描述、价格和图片。这就意味着只抓取一个索引页,就能抽取其中的30个条目以及前往下一页的链接。通过爬取100个索引页,我们只需要100个请求,而不是3000个请求,就能够得到3000个条目。太棒了!

在真实的Gumtree网站中,索引页的描述信息要比列表页中完整的描述信息稍短一些。不过此时这种抓取方式可能也是可行的,甚至也能令人满意。

在我们的例子中,当查看任何一个索引页的HTML代码时,就会发现索引页中的每个房源都有其自己的节点,并使用<code>itemtype="http://schema.org/Product"</code>来表示。在该节点中,我们拥有与详情页完全相同的方式为每个属性注解的所有信息,如图5.4所示。

图5.4 从单一索引页抽取多个房产信息

我们在Scrapy shell中加载第一个索引页,并使用XPath表达式进行测试。

&lt;strong&gt;$ scrapy shell http://web:9312/properties/index_00000.html&lt;/strong&gt;

在Scrapy shell中,尝试选取所有带有Product标签的内容:

&lt;strong&gt;&gt;&gt;&gt; p=response.xpath('//*[@itemtype="http://schema.org/Product"]')&lt;/strong&gt;&lt;strong&gt;&gt;&gt;&gt; len(p)&lt;/strong&gt;&lt;strong&gt;30&lt;/strong&gt;&lt;strong&gt;&gt;&gt;&gt; p&lt;/strong&gt;&lt;strong&gt;[&amp;lt;Selector xpath='//*[@itemtype="http://schema.org/Product"]' data=u'&amp;lt;li &lt;/strong&gt;&lt;strong&gt;class="listing-maxi" itemscopeitemt'...]&lt;/strong&gt;

可以看到我们得到了一个包含30个<code>Selector</code>对象的列表,每个对象指向一个房源。在某种意义上,<code>Selector</code>对象与<code>Response</code>对象有些相似,我们可以在其中使用XPath表达式,并且只从它们指向的地方获取信息。唯一需要说明的是,这些表达式应该是相对XPath表达式。相对XPath表达式与我们之前看到的基本一样,不过在前面增加了一个'.'点号。举例说明,让我们看一下使用<code>.//*[@itemprop="name"][1]/text()</code>这个相对XPath表达式,从第4个房源抽取标题时是如何工作的。

&lt;strong&gt;&gt;&gt;&gt; selector = p[3]&lt;/strong&gt;&lt;strong&gt;&gt;&gt;&gt; selector&lt;/strong&gt;&lt;strong&gt;&amp;lt;Selector xpath='//*[@itemtype="http://schema.org/Product"]' ... '&gt;&lt;/strong&gt;&lt;strong&gt;&gt;&gt;&gt; selector.xpath('.//*[@itemprop="name"][1]/text()').extract()&lt;/strong&gt;&lt;strong&gt;[u'l fun broadband clean people brompton european']&lt;/strong&gt;

可以在<code>Selector</code>对象的列表中使用<code>for</code>循环,抽取索引页中全部30个条目的信息。

为了实现该目的,我们再一次从第3章的<code>manual.py</code>着手,将爬虫重命名为"fast",并重命名文件为<code>fast.py</code>。我们将复用大部分代码,只在<code>parse()</code>和<code>parse_items()</code>方法中进行少量修改。最新方法的代码如下。

defparse(self, response):  # Get the next index URLs and yield Requests  next_sel = response.xpath('//*[contains(@class,"next")]//@href')  for url in next_sel.extract():    yield Request(urlparse.urljoin(response.url, url))  # Iterate through products and create PropertiesItems  selectors = response.xpath(    '//*[@itemtype="http://schema.org/Product"]')  for selector in selectors:    yieldself.parse_item(selector, response)

在代码的第一部分中,对前往下一个索引页的<code>Request</code>的<code>yield</code>操作的代码没有变化。唯一改变的内容在第二部分,不再使用<code>yield</code>为每个详情页创建请求,而是迭代选择器并调用<code>parse_item()</code>。其中,<code>parse_item()</code>的代码也和原始代码非常相似,如下所示。

def parse_item(self, selector, response):  # Create the loader using the selector  l = ItemLoader(item=PropertiesItem(), selector=selector)  # Load fields using XPath expressions  l.add_xpath('title', './/*[@itemprop="name"][1]/text()',        MapCompose(unicode.strip, unicode.title))  l.add_xpath('price', './/*[@itemprop="price"][1]/text()',        MapCompose(lambda i: i.replace(',', ''), float),        re='[,.0-9]+')  l.add_xpath('description',        './/*[@itemprop="description"][1]/text()',        MapCompose(unicode.strip), Join())  l.add_xpath('address',        './/*[@itemtype="http://schema.org/Place"]'        '[1]/*/text()',        MapCompose(unicode.strip))  make_url = lambda i: urlparse.urljoin(response.url, i)  l.add_xpath('image_urls', './/*[@itemprop="image"][1]/@src',        MapCompose(make_url))  # Housekeeping fields  l.add_xpath('url', './/*[@itemprop="url"][1]/@href',        MapCompose(make_url))  l.add_value('project', self.settings.get('BOT_NAME'))  l.add_value('spider', self.name)  l.add_value('server', socket.gethostname())  l.add_value('date', datetime.datetime.now())  return l.load_item()

我们所做的细微变更如下所示。

<code>ItemLoader</code>现在使用<code>selector</code>作为源,而不再是<code>Response</code>。这是<code>ItemLoader</code> API一个非常便捷的功能,能够让我们从当前选取的部分(而不是整个页面)抽取数据。

XPath表达式通过使用前缀点号(.)转为相对XPath。

我们必须自己编辑<code>Item</code>的URL。之前,<code>response.url</code>已经给出了房源页的URL。而现在,它给出的是索引页的URL,因为该页面才是我们要爬取的。我们需要使用熟悉的<code>.//*[@itemprop="url"][1]/@href</code>这个XPath表达式抽取出房源的URL,然后使用<code>MapCompose</code>处理器将其转换为绝对URL。

小的改变能够节省巨大的工作量。现在,我们可以使用如下代码运行该爬虫。

&lt;strong&gt;$ scrapy crawl fast -s CLOSESPIDER_PAGECOUNT=3&lt;/strong&gt;&lt;strong&gt;...&lt;/strong&gt;&lt;strong&gt;INFO: Dumping Scrapy stats:&lt;/strong&gt;&lt;strong&gt;  'downloader/request_count': 3, ...&lt;/strong&gt;&lt;strong&gt;  'item_scraped_count': 90,...&lt;/strong&gt;

和预期一样,只用了3个请求,就抓取了90个条目。如果我们没有在索引页中获取到的话,则需要93个请求。这种方式太明智了!

如果你想使用<code>scrapy parse</code>进行调试,那么现在必须设置<code>spider</code>参数,如下所示。

&lt;strong&gt;$ scrapy parse --spider=fast http://web:9312/properties/index_00000.html&lt;/strong&gt;&lt;strong&gt;...&lt;/strong&gt;&lt;strong&gt;&gt;&gt;&gt; STATUS DEPTH LEVEL 1 &amp;lt;&amp;lt;&amp;lt;&lt;/strong&gt;&lt;strong&gt;# Scraped Items --------------------------------------------&lt;/strong&gt;&lt;strong&gt;[{'address': [u'Angel, London'],&lt;/strong&gt;&lt;strong&gt;... 30 items...&lt;/strong&gt;&lt;strong&gt;# Requests ---------------------------------------------------&lt;/strong&gt;&lt;strong&gt;[&amp;lt;GET http://web:9312/properties/index_00001.html&gt;]&lt;/strong&gt;

正如期望的那样,<code>parse()</code>返回了<code>30</code>个<code>Item</code>以及一个前往下一索引页的<code>Request</code>。请使用<code>scrapy parse</code>随意试验,比如传输<code>--depth=2</code>。

本文摘自《精通Python爬虫框架Scrapy》

Scrapy是使用Python开发的一个快速、高层次的屏幕抓取和Web抓取框架,用于抓Web站点并从页面中提取结构化的数据。本书以Scrapy 1.0版本为基础,讲解了Scrapy的基础知识,以及如何使用Python和三方API提取、整理数据,以满足自己的需求。 

延伸推荐

<a href="http://mp.weixin.qq.com/s?__biz=MzA3NTIzMzIxNQ==&amp;mid=2652796138&amp;idx=1&amp;sn=b8387b4f03c997d395a5c22204c389fd&amp;chksm=8499763ab3eeff2c022078c646ad2e1d9add10b9898a1609bf5c378d756102567da35e6f5528&amp;scene=21#wechat_redirect">AI经典书单| 入门人工智能该读哪些书?</a>

点击关键词阅读更多新书:

在“异步图书”后台回复“关注”,即可免费获得2000门在线视频课程;推荐朋友关注根据提示获取赠书链接,免费得异步图书一本。赶紧来参加哦!

点击阅读原文,查看本书更多信息

扫一扫上方二维码,回复“关注”参与活动!

<a href="http://mp.weixin.qq.com/s?__biz=MjM5NzUzODI1Mg==&amp;mid=2652540878&amp;idx=1&amp;sn=2183abfb7eb56ec5c7c1c3bb404669d5&amp;chksm=bd365f6d8a41d67b461e483aa5a760178b099f2ada8eda905e6d768345ca51cb23a70c80c172&amp;scene=21#wechat_redirect"></a>

<a href="http://mp.weixin.qq.com/s?__biz=MjM5NzUzODI1Mg==&amp;mid=2652541846&amp;idx=1&amp;sn=c365b93616eeaa0479b4a8e5044cee16&amp;chksm=bd3643358a41ca23587c3ad3d58f8f1c6d99f86d190cb8260bb01081d4fa770783d74f920087&amp;scene=21#wechat_redirect"></a>

<a href="http://www.epubit.com.cn/book/details/4224"></a>

<a href="http://mp.weixin.qq.com/s?__biz=MjM5NzUzODI1Mg==&amp;mid=2652541195&amp;idx=1&amp;sn=a1662cfe0dedc99121159c7b2647b974&amp;chksm=bd3641a88a41c8befb8eb7583b777eaf131255e42328cb1c4dbdce24ffe8d259fd67aba9b34e&amp;scene=21#wechat_redirect"></a>

<a href="http://www.epubit.com.cn/book/details/4851"></a>

<a href="http://mp.weixin.qq.com/s?__biz=MzA3NTIzMzIxNQ==&amp;mid=2652796129&amp;idx=1&amp;sn=802612ccc4fb2a21153b9bbc5aaa32a8&amp;chksm=84997631b3eeff27ce5f814b2960666e62d61c723bece371d1530b3752a6cf2f7c24637f58de&amp;scene=21#wechat_redirect"></a>

<a href="http://mp.weixin.qq.com/s?__biz=MjM5NzUzODI1Mg==&amp;mid=2652541116&amp;idx=1&amp;sn=9438ac3e1085af59a2c3e09e291fb23c&amp;chksm=bd36401f8a41c9094c12cf0c500100a289d068afa97b7d6e925a17d2b020a460e7e612977747&amp;scene=21#wechat_redirect"></a>

<a href="http://mp.weixin.qq.com/s?__biz=MzA3NTIzMzIxNQ==&amp;mid=2652796021&amp;idx=1&amp;sn=476082e59a1d3800429711e91c50fccb&amp;chksm=849976a5b3eeffb39a33fc42f570d893da1e1947e151482fd374a69f29e8f62a3eda6c7f1e75&amp;scene=21#wechat_redirect"></a>

<a href="http://mp.weixin.qq.com/s?__biz=MjM5NzUzODI1Mg==&amp;mid=2652540968&amp;idx=1&amp;sn=48891f452290c20e8113428980a3b568&amp;chksm=bd36408b8a41c99dcc38a00b07db0dd4c1f55843329c47f2c4f0064d9b2c5db096cce049b9ba&amp;scene=21#wechat_redirect"></a>

<a href="http://mp.weixin.qq.com/s?__biz=MjM5NzUzODI1Mg==&amp;mid=2652542107&amp;idx=1&amp;sn=5d45a9f602fbbfe4bbb7f9d5c86237bc&amp;chksm=bd3644388a41cd2e3a2a7676000c63c6aa58b242d7a6d80aa7975b19a07dab9d7612d7948308&amp;scene=21#wechat_redirect"></a>

<a href="http://mp.weixin.qq.com/s?__biz=MzA3NTIzMzIxNQ==&amp;mid=2652796261&amp;idx=1&amp;sn=5bf38707a5ad9223313e0712202b30fe&amp;chksm=849977b5b3eefea3813796c48854b67b65c413204266da7a20bf81d8aa4a2ec7a02364130134&amp;scene=21#wechat_redirect"></a>

<a href="https://item.jd.com/12273412.html"></a>

<a href="https://item.jd.com/12262251.html"></a>

<a href="http://mp.weixin.qq.com/s?__biz=MjM5NzUzODI1Mg==&amp;mid=2652540628&amp;idx=1&amp;sn=8eb0c03f03baad8c29e1ace16c6bfbed&amp;chksm=bd365e778a41d761b2e190c20c3a779d56fca0efbaaf82315af8238a257a09767972e6d155c5&amp;scene=21#wechat_redirect"></a>

<a href="http://mp.weixin.qq.com/s?__biz=MjM5NzUzODI1Mg==&amp;mid=2652541335&amp;idx=1&amp;sn=03ce90ca766f23240be51d86400d3864&amp;chksm=bd3641348a41c822217deeffd30b06f6125ed4d6cdc7ecec1e57bb0761a0ace058ce1f358864&amp;scene=21#wechat_redirect"></a>

<a href="http://www.epubit.com.cn/book/details/4867"></a>

点击下方阅读原文,查看更多内容