CrawlSpiders
通过下面的命令可以快速创建 CrawlSpider模板 的代码:
scrapy genspider -t crawl tencent tencent.com
我们通过正则表达式,制作了新的url作为Request请求参数,现在我们可以用这个...
class scrapy.spiders.CrawlSpider
它是Spider的派生类,Spider类的设计原则是只爬取start_url列表中的网页,而CrawlSpider类定义了一些规则(rule)来提供跟进link的方便的机制,从爬取的网页中获取link并继续爬取的工作更适合。
在网上找了一段源码参考:

1 class CrawlSpider(Spider): 2 rules = () 3 def __init__(self, *a, **kw): 4 super(CrawlSpider, self).__init__(*a, **kw) 5 self._compile_rules() 6 7 #首先调用parse()来处理start_urls中返回的response对象 8 #parse()则将这些response对象传递给了_parse_response()函数处理,并设置回调函数为parse_start_url() 9 #设置了跟进标志位True 10 #parse将返回item和跟进了的Request对象 11 def parse(self, response): 12 return self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True) 13 14 #处理start_url中返回的response,需要重写 15 def parse_start_url(self, response): 16 return [] 17 18 def process_results(self, response, results): 19 return results 20 21 #从response中抽取符合任一用户定义'规则'的链接,并构造成Resquest对象返回 22 def _requests_to_follow(self, response): 23 if not isinstance(response, HtmlResponse): 24 return 25 seen = set() 26 #抽取之内的所有链接,只要通过任意一个'规则',即表示合法 27 for n, rule in enumerate(self._rules): 28 links = [l for l in rule.link_extractor.extract_links(response) if l not in seen] 29 #使用用户指定的process_links处理每个连接 30 if links and rule.process_links: 31 links = rule.process_links(links) 32 #将链接加入seen集合,为每个链接生成Request对象,并设置回调函数为_repsonse_downloaded() 33 for link in links: 34 seen.add(link) 35 #构造Request对象,并将Rule规则中定义的回调函数作为这个Request对象的回调函数 36 r = Request(url=link.url, callback=self._response_downloaded) 37 r.meta.update(rule=n, link_text=link.text) 38 #对每个Request调用process_request()函数。该函数默认为indentify,即不做任何处理,直接返回该Request. 39 yield rule.process_request(r) 40 41 #处理通过rule提取出的连接,并返回item以及request 42 def _response_downloaded(self, response): 43 rule = self._rules[response.meta['rule']] 44 return self._parse_response(response, rule.callback, rule.cb_kwargs, rule.follow) 45 46 #解析response对象,会用callback解析处理他,并返回request或Item对象 47 def _parse_response(self, response, callback, cb_kwargs, follow=True): 48 #首先判断是否设置了回调函数。(该回调函数可能是rule中的解析函数,也可能是 parse_start_url函数) 49 #如果设置了回调函数(parse_start_url()),那么首先用parse_start_url()处理response对象, 50 #然后再交给process_results处理。返回cb_res的一个列表 51 if callback: 52 #如果是parse调用的,则会解析成Request对象 53 #如果是rule callback,则会解析成Item 54 cb_res = callback(response, **cb_kwargs) or () 55 cb_res = self.process_results(response, cb_res) 56 for requests_or_item in iterate_spider_output(cb_res): 57 yield requests_or_item 58 59 #如果需要跟进,那么使用定义的Rule规则提取并返回这些Request对象 60 if follow and self._follow_links: 61 #返回每个Request对象 62 for request_or_item in self._requests_to_follow(response): 63 yield request_or_item 64 65 def _compile_rules(self): 66 def get_method(method): 67 if callable(method): 68 return method 69 elif isinstance(method, basestring): 70 return getattr(self, method, None) 71 72 self._rules = [copy.copy(r) for r in self.rules] 73 for rule in self._rules: 74 rule.callback = get_method(rule.callback) 75 rule.process_links = get_method(rule.process_links) 76 rule.process_request = get_method(rule.process_request) 77 78 def set_crawler(self, crawler): 79 super(CrawlSpider, self).set_crawler(crawler) 80 self._follow_links = crawler.settings.getbool('CRAWLSPIDER_FOLLOW_LINKS', True)
CrawlSpider继承于Spider类,除了继承过来的属性外(name、allow_domains),还提供了新的属性和方法:
LinkExtractors
class scrapy.linkextractors.LinkExtractor
Link Extractors 的目的很简单: 提取链接。
每个LinkExtractor有唯一的公共方法是 extract_links(),它接收一个 Response 对象,并返回一个 scrapy.link.Link 对象。
Link Extractors要实例化一次,并且 extract_links 方法会根据不同的 response 调用多次提取链接。
1 class scrapy.linkextractors.LinkExtractor( 2 allow = (), 3 deny = (), 4 allow_domains = (), 5 deny_domains = (), 6 deny_extensions = None, 7 restrict_xpaths = (), 8 tags = ('a','area'), 9 attrs = ('href'), 10 canonicalize = True, 11 unique = True, 12 process_value = None 13 )
主要参数:
1 allow:满足括号中“正则表达式”的值会被提取,如果为空,则全部匹配。 2 3 deny:与这个正则表达式(或正则表达式列表)不匹配的URL一定不提取。 4 5 allow_domains:会被提取的链接的domains。 6 7 deny_domains:一定不会被提取链接的domains。 8 9 restrict_xpaths:使用xpath表达式,和allow共同作用过滤链接。
rules
在rules中包含一个或多个Rule对象,每个Rule对爬取网站的动作定义了特定操作。如果多个rule匹配了相同的链接,则根据规则在本集合中被定义的顺序,第一个会被使用。
1 class scrapy.spiders.Rule( 2 link_extractor, 3 callback = None, 4 cb_kwargs = None, 5 follow = None, 6 process_links = None, 7 process_request = None 8 )
-
link_extractor
:是一个Link Extractor对象,用于定义需要提取的链接。 -
callback
: 从link_extractor中每获取到链接时,参数所指定的值作为回调函数,该回调函数接受一个response作为其第一个参数。 -
follow
:是一个布尔(boolean)值,指定了根据该规则从response提取的链接是否需要跟进。 如果callback为None,follow 默认设置为True ,否则默认为False。 -
process_links
:指定该spider中哪个的函数将会被调用,从link_extractor中获取到链接列表时将会调用该函数。该方法主要用来过滤。 -
process_request
:指定该spider中哪个的函数将会被调用, 该规则提取到每个request时都会调用该函数。 (用来过滤request)
注意:当编写爬虫规则时,避免使用parse作为回调函数。由于CrawlSpider使用parse方法来实现其逻辑,如果覆盖了 parse方法,crawl spider将会运行失败。
Logging
Scrapy提供了log功能,可以通过 logging 模块使用。
可以修改配置文件settings.py,任意位置添加下面两行,效果会清爽很多。
LOG_FILE = "TencentSpider.log" LOG_LEVEL = "INFO"
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,启用loggingLOG_ENCODING
默认: 'utf-8',logging使用的编码LOG_FILE
默认: None,在当前目录里创建logging输出文件的文件名LOG_LEVEL
默认: 'DEBUG',log的最低级别LOG_STDOUT
默认: False 如果为 True,进程所有的标准输出(及错误)将会被重定向到log中。例如,执行 print "hello" ,其将会在Scrapy log中显示。
Request

1 # 部分代码 2 class Request(object_ref): 3 4 def __init__(self, url, callback=None, method='GET', headers=None, body=None, 5 cookies=None, meta=None, encoding='utf-8', priority=0, 6 dont_filter=False, errback=None): 7 8 self._encoding = encoding # this one has to be set first 9 self.method = str(method).upper() 10 self._set_url(url) 11 self._set_body(body) 12 assert isinstance(priority, int), "Request priority not an integer: %r" % priority 13 self.priority = priority 14 15 assert callback or not errback, "Cannot use errback without a callback" 16 self.callback = callback 17 self.errback = errback 18 19 self.cookies = cookies or {} 20 self.headers = Headers(headers or {}, encoding=encoding) 21 self.dont_filter = dont_filter 22 23 self._meta = dict(meta) if meta else None 24 25 @property 26 def meta(self): 27 if self._meta is None: 28 self._meta = {} 29 return self._meta
其中,比较常用的参数:
url: 就是需要请求,并进行下一步处理的url callback: 指定该请求返回的Response,由那个函数来处理。 method: 请求一般不需要指定,默认GET方法,可设置为"GET", "POST", "PUT"等,且保证字符串大写 headers: 请求时,包含的头文件。一般不需要。内容一般如下: # 自己写过爬虫的肯定知道 Host: media.readthedocs.org User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:33.0) Gecko/20100101 Firefox/33.0 Accept: text/css,*/*;q=0.1 Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Referer: http://scrapy-chs.readthedocs.org/zh_CN/0.24/ Cookie: _ga=GA1.2.1612165614.1415584110; Connection: keep-alive If-Modified-Since: Mon, 25 Aug 2014 21:59:35 GMT Cache-Control: max-age=0 meta: 比较常用,在不同的请求之间传递数据使用的。字典dict型 request_with_cookies = Request( url="http://www.example.com", cookies={'currency': 'USD', 'country': 'UY'}, meta={'dont_merge_cookies': True} ) encoding: 使用默认的 'utf-8' 就行。 dont_filter: 表明该请求不由调度器过滤。这是当你想使用多次执行相同的请求,忽略重复的过滤器。默认为False。 errback: 指定错误处理函数
Response

1 # 部分代码 2 class Response(object_ref): 3 def __init__(self, url, status=200, headers=None, body='', flags=None, request=None): 4 self.headers = Headers(headers or {}) 5 self.status = int(status) 6 self._set_body(body) 7 self._set_url(url) 8 self.request = request 9 self.flags = [] if flags is None else list(flags) 10 11 @property 12 def meta(self): 13 try: 14 return self.request.meta 15 except AttributeError: 16 raise AttributeError("Response.meta not available, this response " 17 "is not tied to any request")
大部分参数和上面的差不多:
status: 响应码
_set_body(body): 响应体
_set_url(url):响应url
self.request = request
发送POST请求
-
可以使用
yield scrapy.FormRequest(url, formdata, callback)
方法发送POST请求。 -
如果希望程序执行一开始就发送POST请求,可以重写Spider类的
start_requests(self)
方法,并且不再调用start_urls里的url。

1 class mySpider(scrapy.Spider): 2 # start_urls = ["http://www.example.com/"] 3 4 def start_requests(self): 5 url = 'http://www.renren.com/PLogin.do' 6 7 # FormRequest 是Scrapy发送POST请求的方法 8 yield scrapy.FormRequest( 9 url = url, 10 formdata = {"email" : "mr_mao_hacker@163.com", "password" : "axxxxxxxe"}, 11 callback = self.parse_page 12 ) 13 def parse_page(self, response): 14 # do something
Downloader Middlewares
下载中间件是处于引擎(crawler.engine)和下载器(crawler.engine.download())之间的一层组件,可以有多个下载中间件被加载运行。
-
当引擎传递请求给下载器的过程中,下载中间件可以对请求进行处理 (例如增加http header信息,增加proxy信息等);
-
在下载器完成http请求,传递响应给引擎的过程中, 下载中间件可以对响应进行处理(例如进行gzip的解压等)
要激活下载器中间件组件,将其加入到 DOWNLOADER_MIDDLEWARES 设置中。 该设置是一个字典(dict),键为中间件类的路径,值为其中间件的顺序(order)。
这里是一个例子:
DOWNLOADER_MIDDLEWARES = {
'mySpider.middlewares.MyDownloaderMiddleware': 543,
}
编写下载器中间件十分简单。每个中间件组件是一个定义了以下一个或多个方法的Python类:
class scrapy.contrib.downloadermiddleware.DownloaderMiddleware
process_request(self, request, spider)
-
当每个request通过下载中间件时,该方法被调用。
-
process_request() 必须返回以下其中之一:一个 None 、一个 Response 对象、一个 Request 对象或 raise IgnoreRequest:
-
如果其返回 None ,Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器处理函数(download handler)被调用, 该request被执行(其response被下载)。
-
如果其返回 Response 对象,Scrapy将不会调用 任何 其他的 process_request() 或 process_exception() 方法,或相应地下载函数; 其将返回该response。 已安装的中间件的 process_response() 方法则会在每个response返回时被调用。
-
如果其返回 Request 对象,Scrapy则停止调用 process_request方法并重新调度返回的request。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。
-
如果其raise一个 IgnoreRequest 异常,则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录(不同于其他异常那样)。
-
-
参数:
request (Request 对象)
– 处理的requestspider (Spider 对象)
– 该request对应的spider
process_response(self, request, response, spider)
当下载器完成http请求,传递响应给引擎的时候调用
-
process_request() 必须返回以下其中之一: 返回一个 Response 对象、 返回一个 Request 对象或raise一个 IgnoreRequest 异常。
-
如果其返回一个 Response (可以与传入的response相同,也可以是全新的对象), 该response会被在链中的其他中间件的 process_response() 方法处理。
-
如果其返回一个 Request 对象,则中间件链停止, 返回的request会被重新调度下载。处理类似于 process_request() 返回request所做的那样。
-
如果其抛出一个 IgnoreRequest 异常,则调用request的errback(Request.errback)。 如果没有代码处理抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)。
-
-
参数:
request (Request 对象)
– response所对应的requestresponse (Response 对象)
– 被处理的responsespider (Spider 对象)
– response所对应的spider

1 # middlewares.py 2 3 #!/usr/bin/env python 4 # -*- coding:utf-8 -*- 5 6 import random 7 import base64 8 9 from settings import USER_AGENTS 10 from settings import PROXIES 11 12 # 随机的User-Agent 13 class RandomUserAgent(object): 14 def process_request(self, request, spider): 15 useragent = random.choice(USER_AGENTS) 16 17 request.headers.setdefault("User-Agent", useragent) 18 19 class RandomProxy(object): 20 def process_request(self, request, spider): 21 proxy = random.choice(PROXIES) 22 23 if proxy['user_passwd'] is None: 24 # 没有代理账户验证的代理使用方式 25 request.meta['proxy'] = "http://" + proxy['ip_port'] 26 else: 27 # 对账户密码进行base64编码转换 28 base64_userpasswd = base64.b64encode(proxy['user_passwd']) 29 # 对应到代理服务器的信令格式里 30 request.headers['Proxy-Authorization'] = 'Basic ' + base64_userpasswd 31 request.meta['proxy'] = "http://" + proxy['ip_port']
Settings
Scrapy设置(settings)提供了定制Scrapy组件的方法。可以控制包括核心(core),插件(extension),pipeline及spider组件。比如 设置Json Pipeliine、LOG_LEVEL等。
参考文档:http://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/settings.html#topics-settings-ref
内置设置参考手册
-
BOT_NAME
-
默认: 'scrapybot'
-
当您使用 startproject 命令创建项目时其也被自动赋值。
-
-
CONCURRENT_ITEMS
-
默认: 100
-
Item Processor(即 Item Pipeline) 同时处理(每个response的)item的最大值。
-
-
CONCURRENT_REQUESTS
-
默认: 16
-
Scrapy downloader 并发请求(concurrent requests)的最大值。
-
-
DEFAULT_REQUEST_HEADERS
-
默认: 如下
{ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en', }
Scrapy HTTP Request使用的默认header。
-
-
DEPTH_LIMIT
-
默认: 0
-
爬取网站最大允许的深度(depth)值。如果为0,则没有限制。
-
-
DOWNLOAD_DELAY
-
默认: 0
-
下载器在下载同一个网站下一个页面前需要等待的时间。该选项可以用来限制爬取速度, 减轻服务器压力。同时也支持小数:
DOWNLOAD_DELAY = 0.25 # 250 ms of delay
- 默认情况下,Scrapy在两个请求间不等待一个固定的值, 而是使用0.5到1.5之间的一个随机值 * DOWNLOAD_DELAY 的结果作为等待间隔。
-
-
DOWNLOAD_TIMEOUT
-
默认: 180
-
下载器超时时间(单位: 秒)。
-
-
ITEM_PIPELINES
-
默认: {}
-
保存项目中启用的pipeline及其顺序的字典。该字典默认为空,值(value)任意,不过值(value)习惯设置在0-1000范围内,值越小优先级越高。
ITEM_PIPELINES = { 'mySpider.pipelines.SomethingPipeline': 300, 'mySpider.pipelines.ItcastJsonPipeline': 800, }
-
-
LOG_ENABLED
-
默认: True
-
是否启用logging。
-
-
LOG_ENCODING
-
默认: 'utf-8'
-
logging使用的编码。
-
-
LOG_LEVEL
-
默认: 'DEBUG'
-
log的最低级别。可选的级别有: CRITICAL、 ERROR、WARNING、INFO、DEBUG 。
-
-
USER_AGENT
-
默认: "Scrapy/VERSION (+http://scrapy.org)"
-
爬取的默认User-Agent,除非被覆盖。
-
-
PROXIES
: 代理设置-
示例:
PROXIES = [ {'ip_port': '111.11.228.75:80', 'password': ''}, {'ip_port': '120.198.243.22:80', 'password': ''}, {'ip_port': '111.8.60.9:8123', 'password': ''}, {'ip_port': '101.71.27.120:80', 'password': ''}, {'ip_port': '122.96.59.104:80', 'password': ''}, {'ip_port': '122.224.249.122:8088', 'password':''}, ]
-
-
COOKIES_ENABLED = False
- 禁用Cookies
爬取腾讯招聘所有页面的职位信息
先想好需要什么信息:

1 # -*- coding: utf-8 -*- 2 3 # Define here the models for your scraped items 4 # 5 # See documentation in: 6 # https://doc.scrapy.org/en/latest/topics/items.html 7 8 import scrapy 9 10 '''Item 定义结构化数据字段,用来保存爬取到的数据,有点像Python中的dict,但是提供了一些额外的保护减少错误。 11 12 可以通过创建一个 scrapy.Item 类, 并且定义类型为 scrapy.Field的类属性来定义一个Item(可以理解成类似于ORM的映射关系)。''' 13 class MyspiderItem(scrapy.Item): 14 # define the fields for your item here like: 15 #职位名 16 name = scrapy.Field() 17 #详细链接 18 detailLink = scrapy.Field() 19 #职位信息 20 positionInfo = scrapy.Field() 21 #人数 22 peopleNumber = scrapy.Field() 23 #工作地点 24 workLocation = scrapy.Field() 25 #发布时间 26 publishTime = scrapy.Field()
写爬虫代码:(使用框架很简单,其实主要是提取数据)

1 # -*- coding: utf-8 -*- 2 import scrapy 3 from myspider.items import MyspiderItem 4 from scrapy.linkextractors import LinkExtractor 5 from scrapy.spiders import CrawlSpider, Rule 6 7 8 class TencentSpider(CrawlSpider): 9 name = 'tencent' 10 allowed_domains = ['tencent.com'] 11 start_urls = ['http://hr.tencent.com/position.php?&start=0#a'] 12 rules = ( 13 Rule(LinkExtractor(allow=r'position.php?&start=d+'), callback='parse_item', follow=True), 14 ) 15 16 def parse_item(self, response): 17 #i = {} 18 #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract() 19 #i['name'] = response.xpath('//div[@id="name"]').extract() 20 #i['description'] = response.xpath('//div[@id="description"]').extract() 21 #return i 22 for each in response.xpath('//*[@class="even"]'): 23 name = each.xpath('./td[1]/a/text()').extract()[0] 24 detailLink = each.xpath('./td[1]/a/@href').extract()[0] 25 positionInfo = each.xpath('./td[2]/text()').extract()[0] 26 27 peopleNumber = each.xpath('./td[3]/text()').extract()[0] 28 workLocation = each.xpath('./td[4]/text()').extract()[0] 29 publishTime = each.xpath('./td[5]/text()').extract()[0] 30 # print name, detailLink, catalog,recruitNumber,workLocation,publishTime 31 32 item = MyspiderItem() 33 item['name'] = name 34 item['detailLink'] = detailLink 35 item['positionInfo'] = positionInfo 36 item['peopleNumber'] = peopleNumber 37 item['workLocation'] = workLocation 38 item['publishTime'] = publishTime 39 40 yield item

1 import json 2 3 class BaiSispiderPipeline(): 4 def __init__(self): 5 self.filename = open("tencent.json", "w") 6 7 def process_item(self, item, spider): 8 text = json.dumps(dict(item), ensure_ascii = False) + ", " 9 self.filename.write(text.encode('utf8')) 10 return item 11 12 def close_spider(self, spider): 13 self.filename.close()
之前爬取校花网图片的那个,用CrawlSpider,几行代码就可以匹配到所有页面的链接,自己有去重的功能,爬取多页乃至全部

1 # -*- coding: utf-8 -*- 2 import scrapy 3 from scrapy.linkextractors import LinkExtractor 4 from scrapy.spiders import CrawlSpider,Rule 5 from myspider.items import MyspiderItem 6 7 class BaisiSpider(CrawlSpider): 8 name = 'xiaohua' 9 allowed_domains = ['www.521609.com'] 10 page_list = LinkExtractor(allow=('list\d+.html')) 11 start_urls = ['http://www.521609.com/daxuexiaohua/list35.html'] 12 rules = ( 13 Rule(page_list,callback='parseImg',follow=True), 14 ) 15 def parseImg(self, response): 16 17 # 将我们得到的数据封装到一个 `MyspiderItem` 对象 18 item = MyspiderItem() 19 20 #提取数据 21 img_list = response.xpath('//div[@class="index_img list_center"]/ul/li') 22 for img in img_list: 23 img_name = img.xpath('./a/img/@alt')[0].extract() 24 img_url = img.xpath('./a/img/@src')[0].extract() 25 item['img_name'] = img_name 26 item['img_url'] = img_url 27 28 # 将获取的数据交给pipelines 29 yield item 30 31