爬虫框架之Scrapy(三 CrawlSpider)
如何爬取一个网站的全站数据?
可以使用Scrapy中基于Spider的递归方式进行爬取(Request模块回调parse方法)
还有一种更高效的方法,就是基于CrawlSpider的自动爬取实现
简介
CrawlSpider其实是Spider的一个子类,除了继承到Spider的特性和功能外,还派生出了自己独有的强大功能和特性,其中最有名的就是"LInkExtractors"链接提取器,
Spider是所有爬虫的基类,其设计原则只是为了爬取start_url列表中的网页,但是使用从爬取的网页中提取出的url继续爬取的工作,CrawlSpider更加的合适。
创建示例
1 创建爬虫工程 scrapy startproject happy3
2 创建爬虫文件 scrapy genspider -t crawl demo www.qiushibaike.com
-t crawl表示创建的爬虫文件是基于CrawlSpider这个类的而不是Spider基类(当然CrawlSpider也是基于Spider的)
可以看下demo.py
# -*- coding: utf-8 -*-
import scrapy
# 导入模块
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
# 可以它继承与CrawSpider
class DemoSpider(CrawlSpider):
name = 'demo'
allowed_domains = ['www.qiushibaike.com']
start_urls = ['http://www.qiushibaike.com/']
# 提取link的规则
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
LinkExtractor 链接提取器
主要作用是提取response中符合规则的链接。
每个LinkExtractor有唯一的公共方法是 extract_links(),它接收一个 Response 对象,并返回一个 scrapy.link.Link 对象
源码:
def extract_links(self, response):
base_url = get_base_url(response)
if self.restrict_xpaths:
docs = [subdoc
for x in self.restrict_xpaths
for subdoc in response.xpath(x)]
else:
docs = [response.selector]
all_links = []
for doc in docs:
links = self._extract_links(doc, response.url, response.encoding, base_url)
all_links.extend(self._process_links(links))
return unique_list(all_links)
在上面代码中可以看到 LinkExtractor(allow=r'Items/'),它还有其他参数
allow=(),满足括号中的正则表达式则会被提取,如果为空,则全部匹配。
deny=(),与这个正则表达式(或正则表达式列表)不匹配的URL一定不提取。
allow_domains=(),会被提取链接的domains。
deny_domains=(),一定不会被提取链接的domains。
restrict_xpaths=(),使用xpath表达式,和allow共同作用过滤链接。
estrict_css=(),满足css表达式的值会被提取。
rule规则解析器
根据链接提取器中取到的链接,根据指定规则提取解析链接中的内容。
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
参数介绍:
1 指定链接提取器
2 指定解析数据的规则,是一个回调函数
3 是否将链接提取器继续作用到链接提取器提取出的链接网页中,当callback为None,follow默认为true.
注意:当编写爬虫规则时,避免使用parse作为回调函数。由于CrawlSpider使用parse方法来实现其逻辑,如果覆盖了parse方法,爬虫会运行失败。
rules=()
对应不同的规则解析器,一个Rule对象表示一种提取规则。
CrawlSpider的整体流程
1 爬虫文件根据起始url,获取该url的网页内容。
2 链接提取器会根据指定提取规则将网页内容中的链接进行提取。
3 规则解析器会根据指定解析规则将链接提取器中取到的链接中网页内容,根据指定规则解析。
4 讲解析的数据分装到item中,然后交给管道进行持久化操作。
程序示例
# -*- coding: utf-8 -*- import scrapy # 导入模块 from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule # 可以它继承与CrawSpider class DemoSpider(CrawlSpider): name = 'demo' # allowed_domains = ['www.qiushibaike.com'] # 起始url,糗事百科的糗图 start_urls = ['http://www.qiushibaike.com/pic/'] # 提取link的规则 link = LinkExtractor(allow=r'/pic/$') # 提取第一页 link1 = LinkExtractor(allow=r'/pic/page/\d+\?') #其他页面 # rule中存放的是不同的解析规则的解析器 rules = ( Rule(link, callback='parse_item', follow=True), Rule(link1, 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 print(response)
我们可以看到它获取到了这些网站的url:
糗事百科糗图爬取保存到本地
demo.py
# -*- coding: utf-8 -*-
import scrapy
# 导入模块
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from happy3.items import Happy3Item
# 可以它继承与CrawSpider
class DemoSpider(CrawlSpider):
name = 'demo'
# allowed_domains = ['www.qiushibaike.com']
# 起始url,糗事百科的糗图
start_urls = ['http://www.qiushibaike.com/pic/']
# 提取link的规则
link = LinkExtractor(allow=r'/pic/$') # 提取第一页
link1 = LinkExtractor(allow=r'/pic/page/\d+\?') #其他页面
# rule中存放的是不同的解析规则的解析器
rules = (
Rule(link, callback='parse_item', follow=True),
Rule(link1, callback='parse_item', follow=True),
)
# 解析方法
def parse_item(self, response):
div_list = response.xpath('//div[@id="content-left"]/div')
for one_div in div_list:
item = Happy3Item()
item['title'] = one_div.xpath('.//div[@class="content"]/span/text()').extract_first().strip('\n')
item['link'] = one_div.xpath('.//div[@class="thumb"]/a/img/@src').extract_first().strip('\n')
#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()
yield item
item.py
import scrapy
class Happy3Item(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
link = scrapy.Field()
pipelines.py
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
from scrapy.pipelines.images import ImagesPipeline
from scrapy.spiders import Request
import os
# 记录下载记录
class Happy3Pipeline(object):
def create_dir(self, path):
path = path.strip()
if not os.path.exists(path):
os.mkdir(path)
def process_item(self, item, spider):
# 让标题里只保留汉字
# 文件夹名字
apath = './pic/' + ''.join([ i for i in (item['title'])[0:15] if i >= u'\u4e00' and i <= u'\u9fa5'])
# 里面的title的txt文件名字
file_name = (apath + '/' + item['link'].split('/')[-1]).replace('jpg', 'txt')
# 创建文件夹
self.create_dir(apath)
with open(file_name, 'w', encoding='utf-8') as f:
f.write(item['title'])
return item
# 下载图片
class QiushiImagePipeline(ImagesPipeline):
def file_path(self, request, response=None, info=None):
item = request.meta['item']
apath = ''.join([ i for i in (item['title'])[0:15] if i >= u'\u4e00' and i <= u'\u9fa5'])
img_name = item['link'].split('/')[-1]
path = apath + '/' + img_name
return path
def get_media_requests(self, item, info):
url = ['https:' + item['link']]
print(url)
return Request(url=url[0], meta={'item':item})
settings.py
BOT_NAME = 'happy3'
SPIDER_MODULES = ['happy3.spiders']
NEWSPIDER_MODULE = 'happy3.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
# Configure maximum concurrent requests performed by Scrapy (default: 16)
CONCURRENT_REQUESTS = 100
DOWNLOAD_DELAY = 1
ITEM_PIPELINES = {
'happy3.pipelines.Happy3Pipeline': 300,
'happy3.pipelines.QiushiImagePipeline':400,
}
IMAGES_STORE = 'pic'
得到结果:
大概630张图吧,自己练习的话可以少加载点网页。