Scrapy模块入门与实战:笔趣阁小说网爬取

scrapy框架基本使用

创建项目(爬取笔趣阁小说网)

scrapy startproject novels

创建spider

cd novels
scrapy genspider bqgui.cc fffffff

执行 genspider 命令。第一个参数是 Spider 的名称,第二个参数是网站域名。执行完毕之后,内容如下所示:

python 复制代码
import scrapy


class BqguiCcSpider(scrapy.Spider):
    name = "bqgui_cc"
    allowed_domains = ["fffffffffff"]
    start_urls = ["https://fffffffffff"]

    def parse(self, response):
        pass

这里有三个属性------name、allowed_domains 和 start_urls,还有一个方法 parse。

name:它是每个项目唯一的名字,用来区分不同的 Spider。
allowed_domains:它是允许爬取的域名,如果初始或后续的请求链接不是这个域名下的,则请求链接会被过滤掉。防止访问到一些小广告啥的
start_urls:它包含了 Spider 在启动时爬取的 url 列表,初始请求是由它来定义的。
parse:它是 Spider 的一个方法。默认情况下,被调用的 start_urls 里面的链接构成的请求完成下载执行后,返回的响应就会作为唯一的参数传递给这个函数。该方法负责解析返回的响应、提取数据或者进一步生成要处理的请求。

创建item

创建 Item 需要继承 scrapy.Item 类,并且定义类型为 scrapy.Field 的字段。观察目标网站,我们可以获取到的内容有 text、author、tags。

定义 Item,此时将 items.py 修改如下:

python 复制代码
import scrapy
​
class QuoteItem(scrapy.Item):
​
    text = scrapy.Field()
    author = scrapy.Field()
    tags = scrapy.Field()

解析Response

python 复制代码
    def parse(self, response):
        # print(response.text)
        types = response.xpath('//div[@class="nav"]/ul/li/a/@href').extract()[1:9]
        for index, type_url in enumerate(types):
            print(type, index)
            for page in range(1, 31):
                # 访问小说信息的接口
                yield scrapy.Request(f"https://www.bqgui.cc/json?sortid={index}&page={page}",
                                     self.get_info)

使用item

上文定义了 Item,接下来就要使用它了。Item 可以理解为一个字典,不过在声明的时候需要实例化。然后依次用刚才解析的结果赋值 Item 的每一个字段,最后将 Item 返回即可。

python 复制代码
    def get_info(self, response):
        books = response.json()
        item = NovelsItem()
        match = re.search(r'sortid=(\d+)', response.url)
        book_type = match.group(1)
        if len(books) > 0:
            for book in books:
                book_author = book['author']
                book_name = book['articlename']
                book_content = book['intro']
                item['book_author'] = book_author
                item['book_name'] = book_name
                item['book_content'] = book_content
                item['book_type'] = book_type
                yield item

后续requests

构造请求时需要用到 scrapy.Request。这里我们传递两个参数------url 和 callback,这两个参数的说明如下。

url:它是请求链接。
callback:它是回调函数。当指定了该回调函数的请求完成之后,获取到响应,引擎会将该响应作为参数传递给这个回调函数。回调函数进行解析或生成下一个请求,回调函数如上文的 parse() 所示。
python 复制代码
yield scrapy.Request(f"https://www.bqgui.cc/json?sortid={index}&page={page}",
                     self.get_info)

运行

  1. 运行run.py 文件

    python 复制代码
    from scrapy.cmdline import execute
    
    # scrapy crawl 域名
    execute(['scrapy', 'crawl', 'bqgui_cc'])
  2. 命令行运行

    scrapy crawl 爬虫名
    

保存到文件

运行完 Scrapy 后,我们只在控制台看到了输出结果。如果想保存结果该怎么办呢?

要完成这个任务其实不需要任何额外的代码,Scrapy 提供的 Feed Exports 可以轻松将抓取结果输出。例如,我们想将上面的结果保存成 JSON 文件,可以执行如下命令:

python 复制代码
scrapy crawl quotes -o quotes.json

python 复制代码
scrapy crawl quotes -o quotes.jsonlines

输出格式还支持很多种,例如 csv、xml、pickle、marshal 等,还支持 ftp、s3 等远程输出,另外还可以通过自定义 ItemExporter 来实现其他的输出。

例如,下面命令对应的输出分别为 csv、xml、pickle、marshal 格式以及 ftp 远程输出:

python 复制代码
scrapy crawl quotes -o quotes.csv
scrapy crawl quotes -o quotes.xml
scrapy crawl quotes -o quotes.pickle
scrapy crawl quotes -o quotes.marshal
scrapy crawl quotes -o ftp://user:pass@ftp.example.com/path/to/quotes.csv

其中,ftp 输出需要正确配置用户名、密码、地址、输出路径,否则会报错。

通过 Scrapy 提供的 Feed Exports,我们可以轻松地输出抓取结果到文件。对于一些小型项目来说,这应该足够了。不过如果想要更复杂的输出,如输出到数据库等,我们可以使用 Item Pileline 来完成。

使用 Item Pipeline

如果想进行更复杂的操作,如将结果保存到 MongoDB 数据库,或者筛选某些有用的 Item,则我们可以定义 Item Pipeline 来实现。

Item Pipeline 为项目管道。当 Item 生成后,它会自动被送到 Item Pipeline 进行处理,我们常用 Item Pipeline 来做如下操作。

  • 清洗 HTML 数据;
  • 验证爬取数据,检查爬取字段;
  • 查重并丢弃重复内容;
  • 将爬取结果储存到数据库。

要实现 Item Pipeline 很简单,只需要定义一个类并实现 process_item 方法即可。启用 Item Pipeline 后,Item Pipeline 会自动调用这个方法。process_item 方法必须返回包含数据的字典或 Item 对象,或者抛出 DropItem 异常。

process_item 方法有两个参数。一个参数是 item,每次 Spider 生成的 Item 都会作为参数传递过来。另一个参数是 spider,就是 Spider 的实例。

接下来,我们实现一个 Item Pipeline,筛掉 text 长度大于 50 的 Item,并将结果保存到 MongoDB。

修改项目里的 pipelines.py 文件,之前用命令行自动生成的文件内容可以删掉,增加一个 TextPipeline 类,内容如下所示:

python 复制代码
from scrapy.exceptions import DropItem
​
class TextPipeline(object):
    def __init__(self):
        self.limit = 50
    
    def process_item(self, item, spider):
        if item['text']:
            if len(item['text']) > self.limit:
                item['text'] = item['text'][0:self.limit].rstrip() + '...'
            return item
        else:
            return DropItem('Missing Text')

这段代码在构造方法里定义了限制长度为 50,实现了 process_item 方法,其参数是 item 和 spider。首先该方法判断 item 的 text 属性是否存在,如果不存在,则抛出 DropItem 异常;如果存在,再判断长度是否大于 50,如果大于,那就截断然后拼接省略号,再将 item 返回即可。

接下来,我们将处理后的 item 存入 MongoDB,定义另外一个 Pipeline。同样在 pipelines.py 中,我们实现另一个类 MongoPipeline,内容如下所示:

python 复制代码
import pymongo
​
class MongoPipeline(object):
    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
​
    @classmethod
    def from_crawler(cls, crawler):
        return cls(mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DB')
        )
​
    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):
        name = item.__class__.__name__
        self.db[name].insert(dict(item))
        return item
​
    def close_spider(self, spider):
        self.client.close()

MongoPipeline 类实现了 API 定义的另外几个方法。

from_crawler:这是一个类方法,用 @classmethod 标识,是一种依赖注入的方式,方法的参数就是 crawler5,通过 crawler 这个参数我们可以拿到全局配置的每个配置信息,在全局配置 settings.py 中我们可以定义 MONGO_URI 和 MONGO_DB 来指定 MongoDB 连接需要的地址和数据库名称,拿到配置信息之后返回类对象即可。所以这个方法的定义主要是用来获取 settings.py 中的配置的。
open_spider:当 Spider 被开启时,这个方法被调用。在这里主要进行了一些初始化操作。
close_spider:当 Spider 被关闭时,这个方法会调用,在这里将数据库连接关闭。

最主要的 process_item 方法则执行了数据插入操作。

定义好 TextPipeline 和 MongoPipeline 这两个类后,我们需要在 settings.py 中使用它们。MongoDB 的连接信息还需要定义。

我们在 settings.py 中加入如下内容:

python 复制代码
ITEM_PIPELINES = {
   'tutorial.pipelines.TextPipeline': 300,
   'tutorial.pipelines.MongoPipeline': 400,
}
MONGO_URI='localhost'
MONGO_DB='tutorial'

赋值 ITEM_PIPELINES 字典,键名是 Pipeline 的类名称,键值是调用优先级,是一个数字,数字越小则对应的 Pipeline 越先被调用。

再重新执行爬取,命令如下所示:

python 复制代码
scrapy crawl quotes

笔趣阁小说网爬取实战项目

这里提供爬虫核心代码:

python 复制代码
import re

import scrapy
from novels.items import NovelsItem


class BqguiCcSpider(scrapy.Spider):
    name = "bqgui_cc"
    # allowed_domains = ["bqgui.cc/"]
    start_urls = ["https://www.bqgui.cc/"]

    def parse(self, response):
        # print(response.text)
        types = response.xpath('//div[@class="nav"]/ul/li/a/@href').extract()[1:9]
        for index, type_url in enumerate(types):
            print(type, index)
            for page in range(1, 31):
                # 访问小说信息的接口
                yield scrapy.Request(f"https://www.bqgui.cc/json?sortid={index}&page={page}",
                                     self.get_info)

    def get_info(self, response):
        books = response.json()
        item = NovelsItem()
        match = re.search(r'sortid=(\d+)', response.url)
        book_type = match.group(1)
        if len(books) > 0:
            for book in books:
                book_author = book['author']
                book_name = book['articlename']
                book_content = book['intro']
                item['book_author'] = book_author
                item['book_name'] = book_name
                item['book_content'] = book_content
                item['book_type'] = book_type
                yield item

scrapy框架速度了解

网站如果没有反爬机制,网速没问题,火力全开,一小时20万条数据,速度还是够用的.

爬虫实习常识了解

小公司一般3到4个爬虫工程师

2个服务器

一个服务器部署10到20个爬虫项目

小网站几十万条数据

大网站上百万条数据

更多精致内容:[CodeRealm]