05-创建爬虫
[toc]
8.2.2 创建爬虫
现在,我们要开始编写真正的爬虫代码了,在Scrapy里又被称为 spider 。通过 genspider 命令,传入爬虫名、域名以及可选的模板参数,就可以生成初始模板。
$ scrapy genspider country_or_district example.python-scraping.com --template= crawl
我们使用了内置的 crawl 模板,以利用Scrapy库的 CrawlSpider 。相对于简单的抓取爬虫来说,Scrapy的 CrawlSpider 拥有一些网络爬取时可用的特殊属性和方法。
运行 genspider 命令之后,下面的代码将会在 example/spiders/country_or_district.py 中自动生成。
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class CountryOrDistrictSpider(CrawlSpider):
name = 'country_or_district'
allowed_domains = ['example.python-scraping.com']
start_urls = ['http://example.python-scraping.com']
rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item',
follow=True),
)
def parse_item(self, response):
i = {}
#i['domain_id'] =
response.xpath('//input[@id="sid"]/@value').extract()
#i['name'] = response.xpath('//div[@id="name"]').extract()
#i['description'] =
response.xpath('//div[@id="description"]').extract()
return i
最开始几行导入了后面会用到的Scrapy库以及编码定义。然后创建了一个爬虫类,该类包括如下类属性。
name:识别爬虫的字符串。allowed_domains:可以爬取的域名列表。如果没有设置该属性,则表示可以爬取任何域名。start_urls:爬虫起始URL列表。rules:该属性为一个通过正则表达式定义的Rule对象元组,用于告知爬虫需要跟踪哪些链接以及哪些链接包含待抓取的有用内容。
你会发现定义的 Rule 中包含一个 callback 属性,该回调被设置为下面定义的 parse_item 。该方法是 CrawlSpider 对象的主要数据抽取方法,并且该方法生成的Scrapy代码中包含从页面中抽取内容的示例。
由于Scrapy是一个高级框架,因此即使只有这几行代码,也还有很多需要了解的知识。官方文档中包含了创建爬虫相关的更多细节,其网址为 http://doc.scrapy.org/en/latest/topics/spiders.html 。
1.优化设置
在运行前面生成的爬虫之前,需要更新Scrapy的设置,避免爬虫被封禁。默认情况下,Scrapy对同一域名允许最多16个并发下载,并且两次下载之间没有延时,这样就会比真实用户浏览时的速度快很多。该行为很容易被服务器检测到并阻止。
在第1章中提到,当下载速度持续高于每秒一个请求时,我们抓取的示例网站会暂时封禁爬虫,也就是说使用默认配置会造成我们的爬虫被封禁。除非你在本地运行示例网站,否则我建议在 example/settings.py 文件中添加如下几行代码,使爬虫同时只能对每个域名发起一个请求,并且每两次请求之间存在合理的5秒延时。
CONCURRENT_REQUESTS_PER_DOMAIN = 1
DOWNLOAD_DELAY = 5
你也可以在文档中搜索到这些设置,使用上面的值进行修改并取消注释。请注意,Scrapy在两次请求之间的延时并不是精确的,这是因为精确的延时同样会造成爬虫容易被检测到,然后被封禁。而Scrapy实际使用的方法是在两次请求之间的延时上添加随机的偏移量。
要想了解关于上述设置和其他可用设置的更多细节,可以参考 `http://doc.scrapy.org/en/latest/topics/settings.html` 。
2.测试爬虫
想要从命令行运行爬虫,需要使用 crawl 命令,并且带上爬虫的名称。
$ scrapy crawl country_or_district -s LOG_LEVEL=ERROR
$
脚本运行后,完全没有输出。你会注意到命令中有一个 -s LOG_LEVEL=ERROR 标记,这是一个Scrapy设置,等同于在 settings.py 文件中定义 LOG_LEVEL = 'ERROR' 。默认情况下,Scrapy会在终端上输出所有日志信息,而这里是将日志级别提升至只显示错误信息。
为了真正抓取页面上的一些内容,我们需要在爬虫文件中添加几行代码。为了确保我们可以启动构建并且抽取item,我们必须先从使用 CountryItem 开始,并更新爬取规则。下面是更新后的爬虫版本。
from example.items import CountryOrDistrictItem
...
rules = (
Rule(LinkExtractor(allow=r'/index/'), follow=True),
Rule(LinkExtractor(allow=r'/view/'), callback='parse_item')
)
def parse_item():
i = CountryOrDistrictItem ()
...
为了抽取结构化数据,需要使用我们创建的 CountryOrDistrictItem 类。在新添加的代码中,我们引入该类,并在 parse_item 方法中实例化了一个对象 i (或item)。
此外,我们还需要添加规则,以便我们的爬虫可以找到数据并对其进行抽取。默认规则为搜索url模式 r'/Items' ,这与我们的示例站点并不匹配。我们可以根据对站点的已知信息,创建两条新规则来替代默认规则。第一条规则爬取索引页并跟踪其中的链接,而第二条规则爬取国家(或地区)页面并将下载响应传给 callback 函数用于抓取。
下面让我们把日志级别设为 DEBUG 以显示更多的爬取信息,来看一下这个改进后的爬虫是如何运行的。
$ scrapy crawl country_or_district -s LOG_LEVEL=DEBUG
...
2017-03-24 11:52:42 [scrapy.core.engine] DEBUG: Crawled (200) <GET
http://example.python-scraping.com/view/Belize-23> (referer:
http://example.python-scraping.com/index/2)
2017-03-24 11:52:49 [scrapy.core.engine] DEBUG: Crawled (200) <GET
http://example.python-scraping.com/view/Belgium-22> (referer:
http://example.python-scraping.com/index/2)
2017-03-24 11:52:53 [scrapy.extensions.logstats] INFO: Crawled 40 pages (at
10 pages/min), scraped 0 items (at 0 items/min)
2017-03-24 11:52:56 [scrapy.core.engine] DEBUG: Crawled (200) <GET
http://example.python-scraping.com/user/login?_next=%2Findex%2F0> (referer:
http://example.python-scraping.com/index/0)
2017-03-24 11:53:03 [scrapy.core.engine] DEBUG: Crawled (200) <GET
http://example.python-scraping.com/user/register?_next=%2Findex%2F0> (referer:
http://example.python-scraping.com/index/0)
...
输出的日志信息显示,索引页和国家(或地区)页都可以正确爬取,并且已经过滤了重复链接。我们还可以看到,在首次启动爬取时,我们已安装的中间件以及其他重要信息的输出。
不过,我们还会发现爬虫浪费了很多资源来爬取每个网页上的登录和注册表单链接,因为它们也匹配 rules 里的正则表达式。前面命令中的登录URL以 _next=%2Findex%2F1 结尾,也就是 _next=/index/1 经过URL编码后的结果,定义了登录后重定向的地址。要想避免爬取这些URL,我们可以使用规则的 deny 参数,该参数同样需要一个正则表达式,用于匹配每个不想爬取的URL。
下面对之前的代码进行了修改,通过避免URL包含 /user/ 来防止爬取用户登录和注册表单。
rules = (
Rule(LinkExtractor(allow=r'/index/', deny=r'/user/'), follow=True),
Rule(LinkExtractor(allow=r'/view/', deny=r'/user/'),
callback='parse_item')
)
想要进一步了解如何使用 `LinkExtractor` 类,可以参考其文档,网址为 `http://doc.scrapy.org/en/latest/topics/linkextractors.html` 。
要想停止当前爬取,并使用新的代码重新开始,你可以使用Ctrl + C或cmd + C发送一个退出信号。之后,你将会看到类似如下所示的信息。
2017-03-24 11:56:03 [scrapy.crawler] INFO: Received SIG_SETMASK, shutting
down gracefully. Send again to force
它将完成队列中的请求,然后停止。你将会在结尾处看到一些额外的统计和调试信息,我们将在本节后面的部分对其进行介绍。
除了为爬虫添加拒绝规则外,你还可以对 `Rule` 对象使用 `process_links` 参数。它将允许你创建一个可以迭代所有可发现链接并进行任意修改的函数(比如移除或添加查询字符串的部分)。关于爬取规则的更多信息,可以查阅文档,地址为 `https://doc.scrapy.org/en/latest/topics/spiders.html#crawling-rules` 。
要想了解关于上述设置和其他可用设置的更多细节,可以参考 `http://doc.scrapy.org/en/latest/topics/settings.html` 。
想要进一步了解如何使用 `LinkExtractor` 类,可以参考其文档,网址为 `http://doc.scrapy.org/en/latest/topics/linkextractors.html` 。
除了为爬虫添加拒绝规则外,你还可以对 `Rule` 对象使用 `process_links` 参数。它将允许你创建一个可以迭代所有可发现链接并进行任意修改的函数(比如移除或添加查询字符串的部分)。关于爬取规则的更多信息,可以查阅文档,地址为 `https://doc.scrapy.org/en/latest/topics/spiders.html#crawling-rules` 。