1 Scrapy 爬虫框架组件详细介绍(二)
1.1 CrawlSpiders
20.1.1 爬虫CrawlSpiders类介绍
CrawlSpiders 类它是 Spider 的派生类,Spider 类的设计原则是只爬取 start_url 列表中的网页,而 CrawlSpider 类定义了一些规则( rule )来提供跟进 link 的方便机制,从爬取的网页中获取 link,并继续爬取工作,比Spiders 类模板爬虫更加适合。
前面案例创建爬虫,使用的命令如下:
scrapy genspider tencents " tencent.com "
默认使用”basic”template(模板)去创建爬虫。
如果想创建 CrawlSpider 类爬虫,可以通过下面的命令指定 crawl 模板可以快速创建 CrawlSpider 模板的爬虫。
scrapy genspider -t crawl tencents " tencent.com "
【-t 】: 指定模板。
例子:
在 DOS 下切换到下面目录。
D:\\scrapy_project\\tencent\\tencent\\spiders
执行命令:
scrapy genspider -t crawl tencents " tencent.com "
注意:命令创建的时候,爬虫文件名不能和作业域名相同,可以创建之后再改回来。
查看生成的 tencent.py 爬虫文件。
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class TencentSpider(CrawlSpider):
name = 'tencent'
allowed_domains = [' tencent.com ']
start_urls = ['http:// tencent.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
从生成的 tencent.py 爬虫文件和之前的Spider基类爬虫文件比较,多了如下几部分内容:
(一)需要导入 LinkExtractor、CrawlSpider、 Rule库
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
(二)爬虫类继承的是 CrawlSpider 类
(三)增加了 rules 。
(四)增加了 LinkExtractor(allow=r'Items/')
(五)新增加了一个回调函数 parse_item(self, response)
20.1.2 CrawlSpider源码
查看CrawlSpider源码。
class CrawlSpider(Spider):
rules = ()
def __init__(self, *a, **kw):
super(CrawlSpider, self).__init__(*a, **kw)
self._compile_rules()
def parse(self, response):
return self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True)
def parse_start_url(self, response):
return []
def process_results(self, response, results):
return results
def _build_request(self, rule, link):
r = Request(url=link.url, callback=self._response_downloaded)
r.meta.update(rule=rule, link_text=link.text)
return r
def _requests_to_follow(self, response):
if not isinstance(response, HtmlResponse):
return
seen = set()
for n, rule in enumerate(self._rules):
links = [lnk for lnk in rule.link_extractor.extract_links(response)
if lnk not in seen]
if links and rule.process_links:
links = rule.process_links(links)
for link in links:
seen.add(link)
r = self._build_request(n, link)
yield rule.process_request(r)
def _response_downloaded(self, response):
rule = self._rules[response.meta['rule']]
return self._parse_response(response, rule.callback, rule.cb_kwargs, rule.follow)
def _parse_response(self, response, callback, cb_kwargs, follow=True):
if callback:
cb_res = callback(response, **cb_kwargs) or ()
cb_res = self.process_results(response, cb_res)
for requests_or_item in iterate_spider_output(cb_res):
yield requests_or_item
if follow and self._follow_links:
for request_or_item in self._requests_to_follow(response):
yield request_or_item
def _compile_rules(self):
def get_method(method):
if callable(method):
return method
elif isinstance(method, six.string_types):
return getattr(self, method, None)
self._rules = [copy.copy(r) for r in self.rules]
for rule in self._rules:
rule.callback = get_method(rule.callback)
rule.process_links = get_method(rule.process_links)
rule.process_request = get_method(rule.process_request)
@classmethod
def from_crawler(cls, crawler, *args, **kwargs):
spider = super(CrawlSpider, cls).from_crawler(crawler, *args, **kwargs)
spider._follow_links = crawler.settings.getbool(
'CRAWLSPIDER_FOLLOW_LINKS', True)
return spider
def set_crawler(self, crawler):
super(CrawlSpider, self).set_crawler(crawler)
self._follow_links = crawler.settings.getbool('CRAWLSPIDER_FOLLOW_LINKS', True)
CrawlSpider 继承于 Spider 类,除了继承过来的属性外(name、allow_domains),还提供了新的属性和方法。
20.1.3 LinkExtractors
【class scrapy.linkextractors.LinkExtractor】
Link Extractors 的目的很简单: 提取链接。
每个LinkExtractor有唯一的公共方法是 extract_links(),它接收一个 Response 对象,并返回一个 scrapy.link.Link 对象。
Link Extractors 要实例化一次,并且 extract_links 方法会根据不同的 response 调用多次提取链接。
class scrapy.linkextractors.LinkExtractor(
allow = (),
deny = (),
allow_domains = (),
deny_domains = (),
deny_extensions = None,
restrict_xpaths = (),
tags = ('a','area'),
attrs = ('href'),
canonicalize = True,
unique = True,
process_value = None
)
主要参数:
【allow】:满足括号中“正则表达式”的值会被提取,如果为空,则全部匹配。
【deny】:与这个正则表达式(或正则表达式列表)不匹配的 URL 一定不提取。
【allow_domains】:会被提取的链接的 domains。
【deny_domains】:一定不会被提取链接的 domains。
【restrict_xpaths】:使用 xpath 表达式,和 allow 共同作用过滤链接。
20.1.4 rules
from scrapy.spiders import Rule
查看 Rule 方法实现的源码。
在 rules 中包含一个或多个 Rule 对象,每个 Rule 对爬取网站的动作定义了特定操作。如果多个 rule 匹配了相同的链接,则根据规则在本集合中被定义的顺序,第一个会被使用。
class scrapy.spiders.Rule(
link_extractor,
callback = None,
cb_kwargs = None,
follow = None,
process_links = None,
process_request = None
)
主要参数:
【link_extractor】:是一个 Link Extractor 对象,用于定义需要提取的链接。
【callback】: 从 link_extractor 中每获取到链接时,参数所指定的值作为回调函数,该回调函数接受一个 response 作为其第一个参数。
注意:当编写爬虫规则时,避免使用 parse 作为回调函数。由于 CrawlSpider 使用 parse方法来实现其逻辑,如果覆盖了 parse方法,crawl spider 将会运行失败。
【follow】:是一个布尔( Boolean )值,指定了根据该规则从 response 提取的链接是否需要跟进。 如果 callback 为 None,follow 默认设置为 True ,否则默认为 False。
【process_links】:指定该 spider 中哪个的函数将会被调用,从link_extractor 中获取到链接列表时将会调用该函数。该方法主要用来过滤。
【process_request】:指定该 spider 中哪个的函数将会被调用, 该规则提取到每个request 时都会调用该函数。 (用来过滤 request )
20.1.5 爬取规则( Crawling rules )
为了弄清楚爬取规则( Crawling rules )的实现过程,我们以腾讯招聘为例,使用 Scrapy Shell 交互终端一步一步展现实现的过程。
第一步:进入项目的根目录。
D:\\scrapy_project\\tencent\\tencent
执行下列命令来调用 Scrapy Shell 交互终端,发送请求。
scrapy shell "http://hr.tencent.com/position.php?&start=0"
第二步:分析腾讯招聘网站的招聘信息的分页 URL 链接。
position.php?&start=20
position.php?&start=30
position.php?&start=40
position.php?&start=50
position.php?&start=60
每个链接组合 https://hr.tencent.com/ 就是一个完整的腾讯招聘网站的招聘信息的分页 URL 链接地址。
我们要做的就是通过正则表达式把页面里的链接(position.php?&start=20
…)提取出来。
page_lx = LinkExtractor(allow=('position.php?&start=\\d+'))
第三步:获取请求响应的内容。
【response.body】
【response.text】
通常是使用 response.text 显示的是 Unicode 完整的字符串,便于匹配,一般情况,只是打印内容的时候,使用 response.body,需要解析请求的内容时,就使用:response.text。
第四步:导入 LinkExtractor,创建 LinkExtractor 实例对象。
from scrapy.linkextractors import LinkExtractor
page_lx = LinkExtractor(allow=('position.php?&start=\\d+'))
allow : LinkExtractor 对象最重要的参数之一,这是一个正则表达式,必须要匹配这个正则表达式(或正则表达式列表)的URL才会被提取,如果没有给出(或为空), 它会匹配所有的链接。
deny : 用法同 allow,只不过与这个正则表达式匹配的 URL 不会被提取)。它的优先级高于 allow 的参数,如果没有给出(或 None), 将不排除任何链接。
第五步:调用 LinkExtractor 实例的 extract_links() 方法查询匹配结果。
没有查到,显示为:[]
这是由于转义字符的问题引起,修改正则表达式内容,继续重新匹配:
page_lx = LinkExtractor(allow=('position\\.php\\?&start=\\d+'))
page_lx.extract_links(response)
或者可以如下获取链接
page_lx = LinkExtractor(allow = ('start=\\d+'))
page_lx.extract_links(response)
这样我们就可以获取到了所有分页的链接地址了。
20.1.6 编写Item文件
import scrapy
class TencentItem(scrapy.Item):
# define the fields for your item here like:
# 职位名
positionname = scrapy.Field()
# 详情连接
positionlink = scrapy.Field()
# 职位类别
positionType = scrapy.Field()
# 招聘人数
peopleNum = scrapy.Field()
# 工作地点
workLocation = scrapy.Field()
# 发布时间
publishTime = scrapy.Field()
20.1.7 编写爬虫文件
import scrapy,sys,os
# 导入CrawlSpider类和Rule
from scrapy.spiders import CrawlSpider, Rule
# 导入链接规则匹配类,用来提取符合规则的连接
from scrapy.linkextractors import LinkExtractor
path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(path)
from dg_sun.items import SunItem
class SunSpider(CrawlSpider):
name = 'sun'
allowed_domains = ['wz.sun0769.com']
start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=0']
# 投诉分页链接的提取规则,返回的符合匹配规则的链接匹配对象的列表(这里提取的是分页的链接)
pagelink = LinkExtractor(allow=("start=\\d+"))
# 投诉详情页内容链接的提取规则,返回的符合匹配规则的链接匹配对象的列表
Details = LinkExtractor(allow=r'/html/question/\\d+/\\d+.shtml')
#多条 Rule
rules = (
# 提取匹配,并跟进链接(没有 callback 意味着 follow 默认为 True )
Rule(pagelink),
Rule(Details, callback = 'parse_item',follow = True),
)
# 指定的回调函数
def parse_item(self, response):
item = SunItem()
#标题
item['title'] = response.xpath('//div[contains(@class, "pagecenter p3")]//strong/text()').extract()[0]
# 编号
item['id'] = item['title'].split(' ')[-1].split(":")[-1]
# 内容
item['content'] = response.xpath('//div[@class="c1 text14_2"]/text()').extract()[0]
# 链接
item['url'] = response.url
yield item注意:callback 千万不能写 parse()方法,再次强调:由于 CrawlSpider 使用 parse()方法来实现其逻辑,如果覆盖了 parse方法,crawl spider 将会运行失败。
20.1.8 编写管道文件
import json
class TencentPipeline(object):
def __init__(self):
self.filename = open("tencent.json", "wb")
def process_item(self, item, spider):
text = json.dumps(dict(item), ensure_ascii = False) + ",\\n"
self.filename.write(text.encode("utf-8"))
return item
def close_spider(self, spider):
self.filename.close()
管道文件跟之前案例的写法一样,没有变化。
20.1.9 修改 settings 文件
在 settings.py 文件配置里指定刚才编写的管道文件名:TencentPipeline。
下载的数据比较多,需要设定一个下载延时时间,以免下载的数据不全。
设置爬虫请求的默认头信息。
20.1.10 运行爬虫
在 dos下切换到目录
D:\\scrapy_project\\tencent\\tencent\\ 下
通过命令运行爬虫 :scrapy crawl tencent
20.1.11查看爬取的结果
查看新建的 tencent.json 数据文件。
1.2 Logging 日志
Scrapy 提供了 log 功能,可以通过 logging 模块使用。
如果我们想把爬虫的输入日志保存到本地,需要在 settings.py 文件里进行设置。
# 保存日志信息的文件名
LOG_FILE = "tencent_log.log"
# 保存日志等级的设置,LOG_LEVEL(可选的级别有: CRITICAL、 ERROR、WARNING、INFO 、DEBUG )(从高到底)
LOG_LEVEL = " DEBUG "
设置完成之后,重新运行爬虫,结果显示如下。
【Log levels】
Scrapy 提供 5 层 logging 级别:
CRITICAL - 严重错误( critical )。
ERROR - 一般错误(regular errors )。
WARNING - 警告信息( warning messages )。
INFO - 一般信息( informational messages )。
DEBUG - 调试信息( debugging messages )。
【logging设置】
通过在 setting.py 中进行以下设置可以被用来配置 logging:
LOG_ENABLED 默认: True,启用logging
LOG_ENCODING 默认: 'utf-8',logging 使用的编码
LOG_FILE 默认: None,在当前目录里创建 logging 输出文件的文件名
LOG_LEVEL 默认: 'DEBUG',log 的最低级别
LOG_STDOUT 默认: False 如果为 True,进程所有的标准输出(及错误)将会被重定向到 log中。
閱讀更多 聽海8 的文章