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


注:bookspider是爬虫的名字,douban.com是允许爬取的域名,可在终端中输入: scrapy genspider -h 查看更多关于该命令的用法



查看它的源码,可以发现它传进来的是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'


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

0 0