scrapy爬取豆瓣读书数据

近期总结

最近驱动自己学习scrapy的动力主要是,后面可能找与python相关的工作。首先是接触了flask,毕竟自己之前是做javaweb后台,部分概念不离根,从web比较好上手。跟着B站的一个flask教程学习并完成了功能项目地址。项目的数据是调用了http接口获取的,数据来源是豆瓣。项目做完后我便想了解下爬虫,试试把豆瓣的图书数据爬取下来。学习scrapy主要是看了官方文档,参考Google到的一些博客demo。目前了解到的scrapy核心部分应该是spider和pipeline,spider主要对爬取页面进行解析,pipeline中负责入库,过滤数据等操作。以下正文,作为学习scrapy的总结。

scrapy爬取豆瓣图书

爬取逻辑

豆瓣书籍,例如《白夜行》的详情页面如下:

书籍信息.png

喜欢读.png

书籍信息包含了一本书的主要信息,如:作者,出版社等。其中最重要的是isbn,每本书都有唯一的isbn号,后面的爬虫也需要用isbn来进行过滤。
在爬取该页面的时候,先解析书籍信息。再解析喜欢读下面的数据,这样就可以在整个豆瓣读书进行爬取。

具体操作

item:

class DouBanBookItem(scrapy.Item):
    author = scrapy.Field()#response.css('#info > a:nth-child(2)::attr(href)').extract_first()
    binding = scrapy.Field()#response.xpath(u'//span[./text()="装帧:"]/following::text()[1]').extract_first()
    publisher = scrapy.Field()#response.xpath(u'//span[contains(./text(), "出版社:")]/following::text()[1]').extract_first()
    price = scrapy.Field()# response.xpath('//*[@id="info"]/span[8]/following::text()[1]').extract_first()
    pages = scrapy.Field()# response.xpath('//*[@id="info"]/span[7]/following::text()[1]').extract_first()
    pubdate = scrapy.Field() #response.xpath('//*[@id="info"]/span[6]/following::text()[1]').extract_first()
    isbn = scrapy.Field() #  response.xpath('//*[@id="info"]/span[11]/following::text()[1]').extract_first()
    summary = scrapy.Field() #response.css('#link-report > div:nth-child(1) > div > p::text').extract()
    image = scrapy.Field()# response.css('#mainpic > a > img::attr(src)').extract_first()
    title = scrapy.Field() # response.css('#wrapper > h1 > span::text').extract_first()

item主要是定义书籍相关的字段,这个根据自己要哪些数据自行定义

spider:

# -*- coding: utf-8 -*-
import scrapy

from lear_scrapy.items import DouBanBookItem


class DoubanbookSpider(scrapy.Spider):
    name = 'doubanbook'
    allowed_domains = ['book.douban.com']
    start_urls = ['https://book.douban.com/subject/26944962/']

    def parse(self, response):
        item = DouBanBookItem()

        item['author'] = fix_author(response)
        item['binding'] = fix_field(response.xpath(u'//span[./text()="装帧:"]/following::text()[1]').extract_first())
        item['publisher'] = fix_field(response.xpath(
            u'//span[contains(./text(), "出版社:")]/following::text()[1]').extract_first())
        item['price'] = fix_field(response.xpath(
            u'//span[contains(./text(), "定价:")]/following::text()[1]').extract_first())
        item['pages'] = fix_field(response.xpath(
            u'//span[contains(./text(), "页数:")]/following::text()[1]').extract_first())
        item['pubdate'] = fix_field(response.xpath(
            u'//span[contains(./text(), "出版年:")]/following::text()[1]').extract_first())
        item['isbn'] = fix_field(response.xpath(
            u'//span[contains(./text(), "ISBN:")]/following::text()[1]').extract_first())
        item['summary'] = fix_summary(response)
        item['image'] = fix_field(response.css('#mainpic > a > img::attr(src)').extract_first())
        item['title'] = fix_field(response.css('#wrapper > h1 > span::text').extract_first())
        yield item

        likes = response.css('#db-rec-section > div > dl')
        for like in likes:
            like_url = like.css('dt > a::attr(href)').extract_first()
            if like_url:
                yield scrapy.Request(url=like_url, callback=self.parse)


def fix_field(field):
    return field.strip() if field else ''


def fix_author(response):
    # 不同页面的author html有所不同
    author = response.css('#info > a:nth-child(2)::text').extract_first()
    if not author:
        author = response.css('#info > span > a::text').extract_first()
    # 部分书籍如一千零一夜,没有作者
    return author.replace('\n            ', '').strip() if author else '无'


def fix_summary(response):
    summary_list = response.css('#link-report > div:nth-child(1) > div > p::text').extract()
    summary = ''
    for s in summary_list:
        summary += s
    return summary

以上代码关键在与parse()函数的解析过程,中间涉及到css和xpath定位数据,我也调了比较长时间

pipeline:

pipeline有三个,按照调用顺序不同,他们的作用依次是:

  1. DuplicatePipeline:通过书籍的isbn号去redis中查询,如果能查到,说明书籍已经爬取过了,不再爬取;如果查不到,将isbn号存入redis,继续爬取。
  2. DoubanBookPipeline:通过mysql入库
  3. DoubanBookImagePipeline:通过继承scrapy自带的ImagesPipeline实现图片下载(需要在setting中配置IMAGES_STORE = 'D:\\pythonSpace\\douabnmovie_image'来指定图片保存路径)
# -*- 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

import pymysql
import scrapy
from scrapy.exceptions import DropItem
from scrapy.pipelines.images import ImagesPipeline

from lear_scrapy.items import DouBanBookItem
from lear_scrapy.util.redisclient import RedisClient


class DuplicatePipeline(object):
    def __init__(self):
        self.redisclient = RedisClient()

    def process_item(self, item, spider):
        if self.redisclient.is_isbn_exist(item['isbn']):
            raise DropItem('书籍:' + item['title'] + ',已经爬取')
        else:
            self.redisclient.save_isbn(item['isbn'])
            return item


class DoubanBookPipeline(object):
    def __init__(self):
        self.connection = pymysql.connect(
            host='localhost',
            port=3306,
            user='root',
            passwd='root',
            db='yushu'
        )
        self.cursor = self.connection.cursor()

    def process_item(self, item, spider):
        insert_sql = 'insert into doubanbook(author,binding,publisher,price,pages,pubdate,isbn,summary,image,title)' \
                     ' values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'
        if isinstance(item, DouBanBookItem):
            values = (item['author'], item['binding'], item['publisher'], item['price'], item['pages'],
                      item['pubdate'], item['isbn'], item['summary'], item['image'], item['title'])
            self.cursor.execute(insert_sql, values)
            self.connection.commit()
        return item


class DoubanBookImagePipeline(ImagesPipeline):
    def get_media_requests(self, item, info):
        if item['image']:
            yield scrapy.Request(item['image'])

    # def item_completed(self, results, item, info):
    #     image_paths = [x['path'] for ok, x in results if ok]
    #     if not image_paths:
    #         raise DropItem("Item contains no files")
    #     item['image_paths'] = image_paths
    #     return item

URL过滤器:

除了过滤重复isbn的书籍以外,还要考虑,对部分已经爬取的页面,不再进行爬取。这通过继承scrapy的RFPDupeFilter来实现,内部也是用了redis来保存爬取的url


from lear_scrapy.util.redisclient import RedisClient


class UrlFilter(RFPDupeFilter):

    redisclient = RedisClient()

    def request_seen(self, request):
        if UrlFilter.redisclient.is_url_crawled(request.url):
            return True
        else:
            UrlFilter.redisclient.add_url(request.url)

上面的功能要启动,需要在setting中配置DUPEFILTER_CLASS = 'lear_scrapy.filter.urlfilter.UrlFilter'

以上者两天折腾的主要东西,爬虫也基本可以使用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 这两天摸索了下scrapy,刚看文档的时候觉得有点生无可恋,scrapy框架个人还是觉得比较难懂的,需要学习的地方...
    Treehl阅读 5,623评论 7 10
  • 前言 scrapy是python界出名的一个爬虫框架。Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应...
    以后的以后_hzh阅读 2,254评论 0 14
  • 背景 部门(东方IC、图虫)业务驱动,需要搜集大量图片资源,做数据分析,以及正版图片维权。前期主要用node做爬虫...
    字节跳动技术团队阅读 7,646评论 1 67
  • 耳机坏掉了,毫无预兆地,不能再像以前那样塞着耳机,单曲循环一首,然后安然入睡。总觉得心里少了些什么,翻来覆去,...
    葵木阅读 307评论 0 0
  • 在今天这样一个时代,物质经济快速发展,互联网信息爆炸增长,我们每一个人其实都变得更加富有了,但同时我们似乎并没有获...
    朱晨铭阅读 673评论 0 6