Python爬虫系列之----Scrapy(九)使用CrawlSpider完善豆瓣图书爬虫
来源:互联网 发布:编程就业方向 编辑:程序博客网 时间:2024/05/24 07:30
接着上一篇文章,剩下的那几个功能未完成,在这片文章中我们通过CrawlSpider来完善它
一、CrawlSpider简介
CrawlSpider是一个比较有用的组件,其通过正则表达匹配对应url并爬取,通过Xpath解析网页内容,再在新页面抽取url继续爬取。CrawlSpider派生自InitSpider派生自BaseSpider(最基本爬虫组件).CrawlSpider主要通过Rule类进行正则表达式的设定,设定相应的回调函数等
1.Rule类如下
class Rule(object): def __init__(self, link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None): self.link_extractor = link_extractor self.callback = callback self.cb_kwargs = cb_kwargs or {} self.process_links = process_links if follow is None: self.follow = False if callback else True else: self.follow = follow
参数:link_extractor 一个url抽取器对象,通过它设定正则,允许匹配什么类型的url,不匹配那些都可以通过它设定
callback 回调函数可传函数的对象 或者函数字符串表现形式,此回调函数主要处理抓取到的数据到Item对象
函数形式为:def 函数名(self,response[,**cb_kwargs])
cb_kwargs 回调函数参数 字典类型
follow 是否继续爬取,true 继续爬取,false 到此停止
process_link 是一个可选的回调函数,接受一个list参数(list元素是通过正则匹配返回url列表),此函数为什么
可选? 主要是处理例如:list 参数里元素url是相对路径,可通过此回调处理加上域名形成一个完整的URL。反正是对路径做一些附加处理。
2.CrawlSpider
CrawlSpider类通过parse函数解析response
def parse(self, response): return self._response_downloaded(response, self.parse_start_url, cb_kwargs={}, follow=True)
parse 函数调用 _response_downloaded函数并返回,返回类型必须是Item 或 Request 对象的列表,这是Scrapy 规定的。再看看_response_downloaded函数
def _response_downloaded(self, response, callback, cb_kwargs, follow): 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 settings.getbool('CRAWLSPIDER_FOLLOW_LINKS', True): for request_or_item in self._requests_to_follow(response): yield request_or_item
函数表达的意思为:
调用callback回调函数(如果callback实例函数有效),否则返回()空元组,callback返回的是item或Request对象list,一开始回调函数为parse_start_url 函数,如果不重写此函数相当于种子页面不处理。默认返回空list调用process_results函数,此函数可重写,如果不重写,返回的是callback返回值,接下来通过follow判断完成通过正则抽取的URL的爬取,此时调用_requests_to_follow 函数
def _requests_to_follow(self, response): seen = set() for rule in self._rules: links = [l for l in rule.link_extractor.extract_links(response) if l not in seen] if links and rule.process_links: links = rule.process_links(links) seen = seen.union(links) for link in links: r = Request(url=link.url) r.meta['link_text'] = link.text r.deferred.addCallback(self._response_downloaded, rule.callback, cb_kwargs=rule.cb_kwargs, follow=rule.follow) yield r
通过response参数传进当前页面信息,seen = set() 防止正则抽取到一样的路径,循环通过对rule对象设置进行正则抽取,并过滤重复的url,接着通过process_links函数(前面提到的URL加工函数)如果该函数对象实例存在。
通过links 列表循环调用Request对象进行进一步抓取。通过ddCallback(self._response_downloaded, rule.callback, cb_kwargs=rule.cb_kwargs, follow=rule.follow) 设回调,self._response_downloaded为回调函数。
3.LinkExtractor
(1)概念:
顾名思义,链接提取器。
(2) 作用:
response对象中获取链接,并且该链接会被接下来爬取。
(3) 使用:
通过SmglLinkExtractor提取希望获取的链接。
classscrapy.contrib.linkextractors.sgml.SgmlLinkExtractor( 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共同作用过滤链接。
列:
# follow = True表示会在返回的url中继续寻找符合条件的urlRule(LinkExtractor(allow='^https://movie\.douban\.com/subject/\d+/reviews$',restrict_xpaths=('//div[@class="review-more"]/a')),follow = True),# callback='parse_content'表示导出的页面由parse_content()函数进行处理。Rule(LinkExtractor(allow='^https://movie\.douban\.com/subject/\d+/reviews.*',restrict_xpaths=('//div[@id="paginator"]/a')),callback='parse_content', follow = True))
二、创建Scrapy项目
scrapy startproject douban_book
创建好项目之后,通过genspider创建CrawlSpider爬虫模板:
scrapy genspider -t crawl bookspider douban.com
查看它的源码,可以发现它传进来的是CrawlSpider,而不是以前的scrapy.Spider!!
# -*- coding: utf-8 -*-import scrapyfrom scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import CrawlSpider, Ruleclass BookspiderSpider(CrawlSpider): name = 'bookspider' allowed_domains = ['douban.com'] start_urls = ['http://douban.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
三、定义items
# -*- coding: utf-8 -*-# Define here the models for your scraped items## See documentation in:# http://doc.scrapy.org/en/latest/topics/items.htmlimport scrapyclass DoubanBookItem(scrapy.Item): """ 定义需要抓取的字段名 """ name = scrapy.Field() # 书名 images = scrapy.Field() # 图片 author = scrapy.Field() # 作者 press = scrapy.Field() # 出版社 date = scrapy.Field() # 出版日期 page = scrapy.Field() # 页数 price = scrapy.Field() # 价格 score = scrapy.Field() # 读者评分 ISBN = scrapy.Field() # ISBN号 author_profile = scrapy.Field() # 作者简介 content_description = scrapy.Field() # 内容简介 link = scrapy.Field() # 详情页链接
四、定义爬虫Spider
1、列表页翻页规律
第一页:https://book.douban.com/tag/心理学?start=0&type=T
第二页:https://book.douban.com/tag/心理学?start=20&type=T
第三页:https://book.douban.com/tag/心理学?start=40&type=T
会发现start后面的数字是有规律递增的,知道这个就好办了,于是用于翻页的url正则表达式我们可以这样写:
r "https://book.douban.com/tag/心理学\?start=\d+&type=T"
注:Python中 r 表示的是原始字符的意思
2、列表页的html源码分析
会发现每一本书的详情页链接都类似,只是后面的一串数字不同而已
https://book.douban.com/subject/26805083/
我们可以得出在列表页中查找图书详情页ulr的正则表达式了
r "http://book.douban.com/subject/\d+/$"
3、书籍详情页html源码分析
我们可以在终端中用以下命令来进行debug,验证我们spider中的代码是否准确无误抓取到了我们想要的字段:
C:\Users\fendo>scrapy shell "https://book.douban.com/subject/26805083/" --nolog[s] Available Scrapy objects:[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)[s] crawler <scrapy.crawler.Crawler object at 0x000002C363CFAD68>[s] item {}[s] request <GET https://book.douban.com/subject/26805083/>[s] response <200 https://book.douban.com/subject/26805083/>[s] settings <scrapy.settings.Settings object at 0x000002C366107B38>[s] spider <DefaultSpider 'default' at 0x2c36638d208>[s] Useful shortcuts:[s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)[s] fetch(req) Fetch a scrapy.Request and update local objects[s] shelp() Shell help (print this help)[s] view(response) View response in a browserIn [1]:
1.图书名
In [1]: response.xpath("//div[@id='wrapper']/h1/span/text()").extract()[0].strip()Out[1]: '心理治疗之路'
2.读者评分
In [7]: response.xpath("//div[@class='rating_self clearfix']/strong/text()").extract()[0].strip()Out[7]: '9.3'
In [8]: response.urlOut[8]: 'https://book.douban.com/subject/26805083/'
4.书籍图片
In [1]: response.xpath("//div[@id='mainpic']/a/img/@src").extract()[0].strip()Out[1]: 'https://img3.doubanio.com/lpic/s28852024.jpg
5.作者,出版社,出版日期,页数,价格,ISBN号,作者简介,内容简介
下面的xpath表达式主要是获取书籍的基本信息
response.xpath("//div[@id='info']//text()").extract()
此时的数据里含有大量的换行符之类的,我们进行去除多余的换行符操作
datas = response.xpath("//div[@id='info']//text()").extract()datas = [data.strip() for data in datas]
列表中还是含有空项,所以我们继续去除空的列表项
datas = [data for data in datas if data != ""]
此时我们打印一下列表中的每一项看看
for i,data in enumerate(datas):print (i,data)
完整的代码:
# -*- coding: utf-8 -*-import scrapyfrom scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import CrawlSpider, Rulefrom scrapy.selector import Selectorfrom douban_book.items import DoubanBookItemimport reimport osimport urllib.requestfrom scrapy.http import HtmlResponse,Requestimport timeclass BookspiderSpider(CrawlSpider): name = 'bookspider' allowed_domains = ['book.douban.com'] start_urls = ['https://book.douban.com/tag/编程?start=0&type=T'] rules = ( # 列表页url Rule(LinkExtractor(allow=(r"https://book.douban.com/tag/编程\?start=\d+&type=T"))), # 详情页url Rule(LinkExtractor(allow=(r"https://book.douban.com/subject/\d+/$")),callback="parse_item") ) def parse_item(self, response): if response.status == 200: print () cks="编程" #类别 cate=response.xpath("//div[@id='db-tags-section']/div/span/a/text()").extract() if cks in cate: sel = Selector(response) item = DoubanBookItem() # 图书名 item["name"] = sel.xpath("//div[@id='wrapper']/h1/span/text()").extract()[0].strip() # 读者评分 item["score"] = sel.xpath("//div[@class='rating_self clearfix']/strong/text()").extract()[0].strip() # 详情页链接 item["link"] = response.url try: # 内容简介 contents = sel.xpath("//div[@id='link-report']//div[@class='intro']")[-1].xpath(".//p//text()").extract() item["content_description"] = "\n".join(content for content in contents) except: item["content_description"] = "" try: # 作者简介 profiles = sel.xpath("//div[@class='related_info']//div[@class='indent ']//div[@class='intro']")[-1].xpath( ".//p//text()").extract() item["author_profile"] = "\n".join(profile for profile in profiles) except: item["author_profile"] = "" src = sel.xpath("//div[@id='mainpic']/a/img/@src").extract()[0].strip() print("images is url-----------------------------------------: " + src) file_name = "%s.jpg" % (item["name"]) # 图书名 file_path = os.path.join("G:\\开发资料\\Python\\douban_book\\img", file_name) # 拼接这个图片的路径 print("file_name---****************************************-file_path---: " + file_name) opener = urllib.request.build_opener() opener.addheaders = [('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1941.0 Safari/537.36')] urllib.request.install_opener(opener) urllib.request.urlretrieve(src, file_path) # 接收文件路径和需要保存的路径,会自动去文件路径下载并保存到我们指定的本地路径 item["images"] = file_path item["tags"] = "编程" datas = response.xpath("//div[@id='info']//text()").extract() datas = [data.strip() for data in datas] datas = [data for data in datas if data != ""] for i, data in enumerate(datas): print ("index %d " % i, data) for data in datas: if u"作者" in data: if u":" in data: item["author"] = datas[datas.index(data) + 1] elif u":" not in data: item["author"] = datas[datas.index(data) + 2] elif u"出版社:" in data: item["press"] = datas[datas.index(data) + 1] elif u"出版年:" in data: item["date"] = datas[datas.index(data) + 1] elif u"页数:" in data: item["page"] = datas[datas.index(data) + 1] elif u"定价:" in data: item["price"] = datas[datas.index(data) + 1] elif u"ISBN:" in data: item["ISBN"] = datas[datas.index(data) + 1] return item
五、Pipeline文件持久化数据
以下代码可以把抓取的到的数据存于json文件中也可以存于MySQL数据库中
# -*- coding: utf-8 -*-# Define your item pipelines here## Don't forget to add your pipeline to the ITEM_PIPELINES setting# See: http://doc.scrapy.org/en/latest/topics/item-pipeline.htmlimport jsonfrom scrapy import logimport pymysql as dbclass DoubanBookPipeline(object): """ 将抓取到的数据存入json文件中 """ def __init__(self): self.file = open("./items.json", "wb") def process_item(self, item, spider): # 由于scrapy在spider中抓取的所有字段都会转换成unicode码 # 所以我们在存入json文件之前先将每一项都转换成utf8 # 不转的话,我们存入json文件中的数据也是unicode码,中文显示方式不是我们想要的 for k in item: item[k] = item[k].encode("utf8") print(item[k]) line = json.dumps(dict(item), ensure_ascii=False) + "\n" self.file.write(line) return itemclass MySQLPipeline(object): def __init__(self): self.con=db.connect(user="root",passwd="123",host="localhost",db="python",charset="utf8") self.cur=self.con.cursor() self.cur.execute('drop table douban_booke') self.cur.execute("create table douban_booke(id int primary key auto_increment,tags varchar(50), name varchar(255) NOT NULL, author varchar(255) NULL, press varchar(100) NULL, date varchar(30) NULL, page varchar(50) NULL, price varchar(30) NULL, score varchar(30) NULL, ISBN varchar(30) NULL, author_profile varchar(1500) NULL, content_description varchar(3000) NULL, link varchar(255) NULL )default charset=utf8;") def process_item(self, item, spider): self.cur.execute("insert into douban_booke(tags, name, author, press, date, page, price, score, ISBN, author_profile,content_description, link) values (%s,%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",(item["tags"], item["name"], item["author"], item["press"], item["date"],item["page"], item["price"], item["score"], item["ISBN"],item["author_profile"], item["content_description"], item["link"])) self.con.commit() return item
在settings中配置下:
ITEM_PIPELINES = {# 'douban_book.pipelines.DoubanBookPipeline': 300, 'douban_book.pipelines.MySQLPipeline': 2,}
并设置下防爬虫策略:
# 为防止被识别为爬虫,应设置下载页面的延时时间DOWNLOAD_DELAY = 2# 模拟浏览器头部DEFAULT_REQUEST_HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", }
运行效果如下:
同时在img目录下生成图片
数据库中的数据
完整示例: http://download.csdn.net/detail/u011781521/9817629
- Python爬虫系列之----Scrapy(九)使用CrawlSpider完善豆瓣图书爬虫
- python爬虫之豆瓣图书信息几行字
- Scrapy-CrawlSpider爬虫组件
- Scrapy-CrawlSpider爬虫组件
- Scrapy-CrawlSpider爬虫组件
- <Python爬虫>爬取豆瓣图书/豆瓣电影系列
- Python爬虫系列之----Scrapy(七)使用IP代理池
- 网络爬虫之Scrapy实战三:爬取多个网页CrawlSpider
- Scrapy爬虫框架学习笔 二 CrawlSpider的使用
- Python爬虫Scrapy框架系列之十四
- [Python爬虫]2.豆瓣图书Top250
- Python爬虫系列之----Scrapy(一)爬虫原理
- python爬虫之Scrapy
- Scrapy豆瓣爬虫
- python爬虫之Scrapy 使用代理配置
- python爬虫之Scrapy 使用代理配置
- scrapy爬虫:CrawlSpider用法与总结
- Scrapy框架利用CrawlSpider创建自动爬虫
- AI 从业者该如何选择深度学习开源框架
- Android6.0系统启动流程分析一:init进程
- c++引用
- 关于char以及varchar2在实际使用时的一点经验
- cocopos 安装
- Python爬虫系列之----Scrapy(九)使用CrawlSpider完善豆瓣图书爬虫
- sort函数的用法
- Android开发实践:Java层与Jni层的数组传递
- 1050. String Subtraction (20)-PAT甲级
- 传统CDN加速不给力,来看适合GIS的Geo-CDN
- kubenetes源码分析之DNS(七)
- 2017去哪儿网前端面试心得
- CMAKE 常用指令
- c++中vector的用法详解