爬虫-豆瓣书籍排行榜及用户信息-2021.7.23-使用Scrapy框架-用MongoDB存储数据

1.环境

python3.8或python3.7

pycharm2021.2

MongoDB

Scrapy

2.信息提取

2.1 创建Scrapy项目

在cmd模式下创建Scrapy项目

# 进入要存放该项目的文件夹下
cd E:\Scrapy Project
# 创建Scrapy项目
scrapy startproject douban
# 进入该项目下,“douban”这个文件夹有一个一级文件夹和二级文件夹,我们只用进入一级文件夹
cd douban
# 创建Spider
scrapy genspider douban_book douban.com

2.2 在Pycharm中打开该项目

打开后,文件树结构为:

请添加图片描述

注意scrapy.cfg配置文件一定在“douban”一级文件夹下,不然在后面运行该爬虫时,会报错。

2.3 项目目标

1.获取按评分排行的书籍排行榜前任意页的书籍基本信息

2.获取每本书籍的读者评论信息和读者主页链接

3.获取读者的地址

4.写入MongoDB数据库

2.4 初始化数据

初始化rank_url、book_comment_url、headers、allowed_domain等信息,其中name是不能修改的。

刚打开douban_book.py,是如下图所示的界面。

请添加图片描述

然后修改上述信息。如下图所示。

请添加图片描述

其中allowed_domain一般情况下不用修改,我这里是测试的时候,发现访问书籍链接地址的时候,其域名是“book.douban.com”,所以添加了进去,其实还有另一种方法,就是在Request函数(很关键的函数)中把域名过滤给关闭掉。

start_urls是在你没有指定访问链接的时候,默认的访问链接,如果有就不会访问该链接。

这里我们有访问的链接,所以我们把start_urls删除了,写上了自己要访问的rank_url,也就是书籍排行榜的访问链接地址,这里有四个,是不同分类的书籍排行榜,分别是文化分类下的“历史”,“哲学”等几个标签,当然也可以继续增加其他访问链接地址。

但是这个链接地址需要进行预处理,也就是如下图勾画的地方

请添加图片描述

我们先找到按评分排行的书籍排行榜第二页(因为第一页一般看不出规律)。如下图

请添加图片描述

然后再到第三页,可以看到start=后面的数字在不断变化,其他都没有变化。

上述写成start={start_rank}是因为字符串的format()方法可以填充该值。

其中book_comments_url也是用相似的方法找到规律后进行的预处理。

2.5 访问书籍排行榜

  def start_requests(self):
        for url in self.rank_url:
            for page in range(0, 30):
                start_rank = page * 20
                yield Request(url.format(start_rank=start_rank), headers=self.headers,
                              callback=self.parse_to_books)

其中Request方法表示使用headers去访问url,返回的信息用callback中的方法进行处理。

2.6 对书籍排行榜的信息进行处理——xpath方法

def parse_to_books(self, response):
    # 从html信息中可以看到每本书籍的基本信息都在单独的subject-item,所以先找到所有的subject-item
    results = response.xpath('//li[@class="subject-item"]')
    # 然后遍历每个subject-item,这里用的results保存的所有subject-item
    for result in results:
        book_href = result.xpath('./div[@class="info"]/h2/a/@href')
        book_name = result.xpath('normalize-space(./div[@class="info"]/h2/a/@title)')
        book_pub = result.xpath('normalize-space(./div[@class="info"]/div[@class="pub"]/text())')
        book_score = result.xpath('./div[@class="info"]/div/span[@class="rating_nums"]/text()')
        book_population = result.xpath('normalize-space(./div[@class="info"]/div/span[@class="pl"]/text())')
	# 用创建BookItem()对象,用其保存书籍的基本信息
        item_book = BookItem()
        item_book['book_name'] = book_name.extract()
        item_book['book_href'] = book_href.extract()
        print(book_href.extract())
        # 这里获取book_id,用于访问书籍的评论列表
        book_id = str(item_book['book_href']).split('/')[-2]
        print(book_id)
        item_book['book_pub'] = book_pub.extract()
        item_book['book_score'] = book_score.extract()
        item_book['book_population'] = book_population.extract()
        # 然后遍历该书籍的评论列表前10页,在不登录的情况下,只能访问前10页内容。
        for page in range(0, 10):
            # 访问书籍评论列表链接,对返回的页面信息用parse_to_reader处理,meta表示传给parse_to_reader会用到的一些数据,是一个字典,可以根据需要继续添加一些数据。
            yield Request(self.book_comments_url.format(book_id=book_id, start_comments=page * 20),
                          headers=self.headers,
                          callback=self.parse_to_reader, meta={'book_href': book_href.extract()})
        yield item_book

2.6 和2.8节会用到BookItem()和ReaderItem_culture(),这几个数据结构在2.9中展示。

2.7 对书籍评论列表页面信息进行处理

这里获取了读者的主页链接、读者的评论,还有传入的meta,也就是书籍的链接,并将上述信息传入到parse_to_reader_address。

def parse_to_reader(self, response):
    results = response.xpath('//li[@class="comment-item"]')
    for result in results:
        reader_href = result.xpath('./div[@class="avatar"]/a/@href').extract()
        reader_comment = result.xpath('./div[@class="comment"]/p[@class="comment-content"]/span['
                                      '@class="short"]/text()').extract()
        book_href = response.meta['book_href']
        yield Request(str(reader_href).strip("['").strip("']"), headers=self.headers,
                      callback=self.parse_to_reader_address,
                      meta={'book_href': book_href, 'reader_href': reader_href,
                            'reader_comment': reader_comment})

其基本原理和2.6节一样。这里就不一一赘述了。

2.8 对读者主页信息进行处理

这里获取了读者的地址信息,其基本原理和上述一样,并将信息传入ReaderItem_culture()对象。

def parse_to_reader_address(self, response):
    print("获取用户地址")
    book_href = str(response.meta['book_href']).strip("['").strip("']")
    reader_href = str(response.meta['reader_href']).strip("['").strip("']")
    reader_comment = str(response.meta['reader_comment'])
    reader_address_raw = response.xpath(
        'normalize-space(//div[@class="basic-info"]/div[@class="user-info"]/a/text())').extract()
    reader_address = str(reader_address_raw).strip("['").strip("']")
    if reader_address.strip() == '':
        print("地址为空")
        return
    item_reader = ReaderItem_culture()
    item_reader['book_href'] = book_href
    item_reader['reader_href'] = reader_href
    item_reader['reader_comment'] = reader_comment
    item_reader['reader_address'] = reader_address
    yield item_reader

2.9 以上用到的Item对象

Item对象在Items.py中申明

import scrapy
class BookItem(scrapy.Item):
    collection = 'douban_books_culture'
    book_name = scrapy.Field()
    book_href = scrapy.Field()
    book_pub = scrapy.Field()
    book_score = scrapy.Field()
    book_population = scrapy.Field()

class ReaderItem_culture(scrapy.Item):
    collection = 'douban_readers_culture'
    book_href = scrapy.Field()
    reader_href = scrapy.Field()
    reader_comment = scrapy.Field()
    reader_address = scrapy.Field()

其中collection表示的是MongoDB的集合名(类似于MySQL中的表名)。

2.10 小结

以上做了这几件事:

1.对豆瓣的书籍基本信息做了提取,并传入到了BookItem()对象。

2.对读者评论信息、主页链接、地址进行了提取,并传入到了ReaderItem_culture对象。

接下来需要将上述提取的信息存入到MongoDB中。

3.存入数据

存入数据的代码在pipeline.py中书写。

import pymongo
import logging

class MongoPipeline(object):
    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    @classmethod
    # 这一步是从settings配置文件里面获取MONGO_URI、MONGO_DB两个自己设置的常量
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DB')
        )

    # 这一步是连接MongoDB数据库
    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def process_item(self, item, spider):
        result = self.db[item.collection].find(item)
        logging.debug("*"*50)
        if result.count() == 0:
            logging.debug("### 无重复、插入数据 ###")
            self.db[item.collection].insert(dict(item))
        else:
            logging.debug("### 有重复、没有插入 ###")
        logging.debug("*"*50)
        return item

    def close_spider(self, spider):
        self.client.close()

4.反爬虫

豆瓣的反爬虫机制很简单,就是看你的访问频率,因此我们仅需要做一个随机延迟访问就可以绕过他的反爬虫机制。

随机延迟在middleware.py中书写。

import logging
import random
import time


class RandomDelayMiddleware(object):
    def __init__(self, delay):
        self.delay = delay

    @classmethod
    def from_crawler(cls, crawler):
        delay = crawler.spider.settings.get("RANDOM_DELAY", 10)
        if not isinstance(delay, int):
            raise ValueError("RANDOM_DELAY need a int")
        return cls(delay)

    def process_request(self, request, spider):
        delay = random.randint(1, self.delay)
        logging.debug("### random delay: %s s ###" % delay)
        time.sleep(delay)

5.其他设置

需要修改settings.py文件

请添加图片描述

还有添加数据库的用户名和密码以及随机延迟上限。

请添加图片描述

最后修改pipeline.py中的类的优先级以及middleware.py中类的优先级。数字越大,优先级越高。由于我们上述两个文件都分别只有一个类,所以下面配置的意思是先执行RandomDelayMiddleware,再执行MongoPipeline。
请添加图片描述

6.一些帮助理解的东西

6.1 Scrapy的编码流程

  1. 先写douban_book.py。这里是进行网页页面信息处理的,包括访问、信息提取等。

  2. 再写items.py,如果douban_book.py中需要用到item,就在这里写。

  3. 接着在middlewares.py中写访问延迟。

  4. 然后再pipeline.py中写对item的存储。

  5. 最后在settings.py中配置,包括关闭ROBOTSTXT_OBEY,一些上述文件用到的常量以及优先级设置。

6.2 Scrapy工作流程

这部分仅是个人理解,各取所需。

其实Scrapy项目的执行过程不仅限于我们的项目之下的文件,还有在Scrapy包中的文件。Scrapy项目由以下几个部分组成:Engine(引擎)、Item(项目)、Scheduler(调度器)、Downloader(下载器)、Spiders(蜘蛛)、Item Pipeline(项目管道)、Downloader Middlewares(下载器中间件)、Spider Middleware(蜘蛛中间件)。

其中

  • Item就是items.py中的东西
  • Spiders就是douban_book.py中的东西
  • Downloader Middlewares一部分在Scrapy包中,一部分就是middlewares.py中的东西
  • Item Pipeline就是pipelines.py中的东西。

其工作方式简单点理解就是如下:

  1. Engine从Spider,也就是douban_book.py开始执行,
  2. 然后Engine从Spider中获取了URL,然后将这些URL放入到了Scheduler调度器,通过Request方法调度。应该类似于操作系统里面的调度。
  3. 接着由调度器传URL给Engine,Engine让Downloader Middlewares处理后发送给Downloader进行下载,也就是页面信息等的下载,Downloader会生成该页面信息的Response,然后将Response通过Downloader Middlewares处理后返回给Spider。
  4. 然后Spider处理Response,提取Item,或者新的Request给Engine。
  5. 最后Engine将返回的Item给Item Pipeline,新的Request给Scheduler。
  6. 重复2-6。

Engine就相当于中介,负责Scheduler、Spider、Downloader、Item Pipeline的联系,然后Engine和Downloader联系过程中需要用到Downloader Middlewares进行处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

豆得儿不是猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值