专栏首页技术进阶之路Python爬虫:Scrapy 框架快速入门及实战演练

Python爬虫:Scrapy 框架快速入门及实战演练

文章目录
  • 一、Scrapy 框架准备
  • 二、快速启动项目
    • 1、创建项目结构
    • 2、创建爬虫
    • 3、更改设置
    • 4、爬虫类分析
    • 5、编写启动脚本
  • 三、爬虫实战
    • 1、初步探索
    • 2、优化数据模型
    • 3、优化数据存储方式
    • 4、爬取多个页面

爬虫系列文章:

  • Python:使用爬虫获取中国最好的大学排名数据(爬虫入门)
  • Python:使用爬虫获取世界大学学术排名存储到 Excel 并作可视化输出
  • 多线程爬虫入门及问题解决(爬取表情包)

今天来介绍一下 Python 的一个爬虫框架Scrapy ,类似的还有 Portia Crawley

本项目源码地址:https://github.com/wsuo/pythonHomework/tree/master/demo

一、Scrapy 框架准备

首先是安装爬虫这个框架:

pip install scrapy

官方文档:https://docs.scrapy.org/en/latest/

如果是 Windows 下安装的,还需要安装一下这个:

pip install pypiwin32

如果是 Ubuntu 上安装,需要提前安装这个:

sudo apt-get install python3-dev build-essential python3-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev

二、快速启动项目

以爬取糗事百科网站为例:https://www.qiushibaike.com/

可以分为以下四步:

scrapy startproject demo
cd demo
scrapy genspider qsbk qiushibaike.com
scrapy crawl qsbk

1、创建项目结构

创建一个名为 demo 的项目,该命令仅仅是创建项目结构,你完全可以自己创建,只不过自动生成方便一些:

scrapy startproject demo

使用 PyCharm 打开一个空的文件夹,然后进入终端输入命令即可:

项目结构如图:

  1. items.py:用来存放爬虫爬取下来数据的模型
  2. middlewares.py:用来存放各种中间件的文件。
  3. pipelines.py:用来将items的模型存储到本地磁盘中。
  4. settings.py:本爬虫的一些配置信息(比如请求头、多久发送一次请求、ip代理池等)。
  5. scrapy.cfg:项目的配置文件
  6. spiders包:以后所有的爬虫,都是存放到这个里面。

2、创建爬虫

先执行进入到工作目录:

cd demo

然后执行命令创建爬虫:

scrapy genspider qsbk qiushibaike.com

然后就会自动生成一个文件qsbk.py

3、更改设置

首先我们要更改一下设置项,为了能够爬到我们想要的数据,我们这里 顶风作案 一下:

修改 ROBOTSTXT_OBEY = False

然后加上请求头的 User-Agent,伪装为浏览器访问:

DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.116 Safari/537.36',
}

4、爬虫类分析

下面我们来看一下爬虫类:

name = 'qsbk'  # 运行的时候输入这个名字,比如
allowed_domains = ['qiushibaike.com']  # 允许的域名
start_urls = ['http://qiushibaike.com/']  # 开始的 url

我们先来试一下:

class QsbkSpider(scrapy.Spider):
    name = 'qsbk'  # 运行的时候输入这个名字
    allowed_domains = ['qiushibaike.com']  # 允许的域名
    start_urls = ['https://www.qiushibaike.com/text/page/1/']  # 开始的 url

    def parse(self, response):
        print('-'*40)
        print(response)
        print(type(response))
        print('-'*40)

运行一下:scrapy crawl qsbk 就可以看到结果了。

<200 https://www.qiushibaike.com/text/page/1/>
<class 'scrapy.http.response.html.HtmlResponse'>

类型为 HtmlResponse

追踪这个类的父类:

可以看到有 xpath 的方法,说明我们可以使用 xpath 解析响应信息。

编写如下代码:

def parse(self, response):
        content = response.xpath('//div[@class="content"]')
        print('-'*40)
        print(response)
        print(type(content))
        print(type(response))
        print('-'*40)

输出:

追踪类SelectorList,找到selector类:

观察该类的方法。

比较重要的方法就是 extract方法可以将 SelectorList类型的转化为列表类型,并且里面装的是字符串,extract_first方法是获取第一个元素。

5、编写启动脚本

由于每次都要输入命令启动挺麻烦的,所以我们可以使用脚本文件执行命令行的命令。

创建一个start.py文件,随便在哪里创建都可以;

内容如下:

from scrapy import cmdline

cmdline.execute(['scrapy', 'crawl', 'qsbk', '--nolog'])

三、爬虫实战

我们先初步的探索,然后慢慢的优化。

1、初步探索

继续上一步的操作,我们在爬虫类QsbkSpider中继续写逻辑。

先来获取一下作者的名称小试牛刀一下,编写代码:

class QsbkSpider(scrapy.Spider):
    name = 'qsbk'  # 运行的时候输入这个名字
    allowed_domains = ['qiushibaike.com']  # 允许的域名
    start_urls = ['https://www.qiushibaike.com/text/page/1/']  # 开始的 url

    def parse(self, response):
        divs = response.xpath('//div[@class="col1 old-style-col1"]/div')
        for div in divs:
            author = div.xpath('./div[@class="author clearfix"]//h2/text()').get().strip()
            print(author)

查看输出结果:

说明这是可以的,下面我们继续爬取后面的内容,比如爬取段子的内容。

content = ''.join(div.xpath('.//div[@class="content"]/span[1]/text()').getall()).strip()

yield 的作用是将函数作为一个生成器返回,以后遍历的时候就会把数据一个一个的拿过去

def parse(self, response):
        divs = response.xpath('//div[@class="col1 old-style-col1"]/div')
        for div in divs:
            author = div.xpath('.//h2/text()').get().strip()
            content = ''.join(div.xpath('.//div[@class="content"]/span[1]/text()').getall()).strip()
            # yield 的作用是将函数作为一个生成器,以后遍历的时候就会把数据一个一个的拿过去
            yield {'昵称': author, '段子内容': content}

然后我们可以存储到本地,这就是需要到管道中执行了,所以打开DemoPipeline类:

class DemoPipeline(object):
    def process_item(self, item, spider):
        return item
    
    def open_spider(self, spider):
        print('爬虫开始')
    
    def close_spider(self, spider):
        print('爬虫结束')

后面两个方法默认是没有的,但是如果我们有文件操作最好可以放在这里面。

但是在使用之前,我们必须要先到配置文件中打开管道:

后面这个值越小越先执行。

编写代码:

import json

class DemoPipeline(object):
    def process_item(self, item, spider):
        json.dump(item, self.file, ensure_ascii=False, indent=4)
        self.file.write('\n')
        return item

    def open_spider(self, spider):
        self.file = open('dz.json', 'a', encoding='utf-8')
        print('爬虫开始')

    def close_spider(self, spider):
        self.file.close()
        print('爬虫结束')

执行结果:

2、优化数据模型

我们之前使用的是自己造的一个字典在爬虫类和管道之间传输,但是更好的做法是使用数据模型,下面我们来实现一下。

定义一个类DemoItem

class DemoItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    author = scrapy.Field()
    content = scrapy.Field()

然后我们在爬虫类中引入这个模型:

import scrapy
# noinspection PyUnresolvedReferences
from demo.items import DemoItem


class QsbkSpider(scrapy.Spider):
    name = 'qsbk'  # 运行的时候输入这个名字
    allowed_domains = ['qiushibaike.com']  # 允许的域名
    start_urls = ['https://www.qiushibaike.com/text/page/1/']  # 开始的 url

    def parse(self, response):
        divs = response.xpath('//div[@class="col1 old-style-col1"]/div')
        for div in divs:
            author = div.xpath('.//h2/text()').get().strip()
            content = ''.join(div.xpath('.//div[@class="content"]/span[1]/text()').getall()).strip()
            # yield 的作用是将函数作为一个生成器,以后遍历的时候就会把数据一个一个的拿过去
            yield DemoItem(author=author, content=content)

好处就是:解耦,约定数据种类,更规范。

但是在管道中获取的时候会有问题,因为他获取的是一个 DemoItem 类型的参数,我们要把它转化为字典类型。

json.dump(dict(item), self.file, ensure_ascii=False, indent=4)

3、优化数据存储方式

优化存储方式肯定要修改管道中的代码,所以我们先注释掉之前写的代码。

使用 scrapy 自带的 JSON 持久化方式。

from scrapy.exporters import JsonItemExporter


class DemoPipeline(object):
    def __init__(self):
        self.file = open('dz.json', 'wb')
        self.exporter = JsonItemExporter(self.file, ensure_ascii=False, encoding='utf-8')

    def open_spider(self, spider):
        print('爬虫开始了')
        # 使用二进制打开
        self.exporter.start_exporting()

    def process_item(self, item, spider):
        self.exporter.export_item(item)
        return item

    def close_spider(self, spider):
        self.exporter.finish_exporting()
        self.file.close()
        print('爬虫结束了')

注意这里必须是文件打开的方式必须是 wb 而且不能指定编码格式。

这样执行的结果就是装在一个列表当中了。

它的执行流程其实就是先将数据塞到一个列表当中,然后调用finish_exporting()写入文件中。

所以他有一个缺陷就是如果 item 比较大的话,他的列表也比较大,一直在内存中就会比较耗内存。

所以我们可以使用JsonLinesItemExporter这个类,他就是不缓存到一个列表当中,直接一行一行的写入文件。

代码如下:

class DemoPipeline(object):
    def __init__(self):
        self.file = open('dz.json', 'wb')
        self.exporter = JsonLinesItemExporter(self.file, ensure_ascii=False, encoding='utf-8')

    def open_spider(self, spider):
        print('爬虫开始了')

    def process_item(self, item, spider):
        self.exporter.export_item(item)
        return item

    def close_spider(self, spider):
        self.file.close()
        print('爬虫结束了')

结果如下:

还有很多文件格式的保存方法,比如 xmlcsvpickel 等等,这里就不再一一演示了。

4、爬取多个页面

我们之前爬的都是单页面,那么怎么才能爬取多页面呢?

我们再来分析一下页面结构:

可以看到最后一个 li 标签中就是页数,但是有一点要注意的就是到最后一页的时候要有一个判断。

我们使用 xpath 来获取 href 的值:

在爬虫类中编写代码:

class QsbkSpider(scrapy.Spider):
    name = 'qsbk'  # 运行的时候输入这个名字
    allowed_domains = ['qiushibaike.com']  # 允许的域名
    start_urls = ['https://www.qiushibaike.com/text/page/1/']  # 开始的 url
    base_domain = 'https://www.qiushibaike.com'

    def parse(self, response):
        divs = response.xpath('//div[@class="col1 old-style-col1"]/div')
        for div in divs:
            author = div.xpath('.//h2/text()').get().strip()
            content = ''.join(div.xpath('.//div[@class="content"]/span[1]/text()').getall()).strip()
            # yield 的作用是将函数作为一个生成器,以后遍历的时候就会把数据一个一个的拿过去
            yield DemoItem(author=author, content=content)
        next_url = response.xpath('//ul[@class="pagination"]//li[last()]/a/@href').get()
        if not next_url:
            return
        else:
            yield scrapy.Request(self.base_domain + next_url, callback=self.parse)

我们在设置页面设置下载的延时,这样的话可以控制爬的速度,因为太快的话容易被发现,而且还有可能把别人服务器搞垮了:

执行结果:

当然这样不是最好的解决方案,后续文章会讲解 CrawlSpider 类的使用。


参考链接:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 多线程爬虫入门及问题解决(爬取表情包)

    我们写这段代码的初衷是因为刚开始的时候 IMG_LIST 可能是 0 ,因为生产者还没来得及做,所以让消费者等一下生产者,所以 continue,但是设想一下到...

    wsuo
  • 计算机网络总结 8800字

    1-07 internet(互联网):通用名词,泛指由多台计算机网络互连而成的网络,协议无特指。Internet(因特网):专用名词,特指采用 TCP/IP 协...

    wsuo
  • 信号量机制实现进程控制

    我们将一次仅允许一个进程访问的资源称为临界资源,而临界区是指访问临界资源的那段代码。

    wsuo
  • 使用html和css实现超赞的屏幕loading

    css部分主要使用了css3中的animation和transform属性。transform属性允许我们对元素进行旋转、缩放、移动或倾斜

    小丑同学
  • HTML炫酷PJAX引导单页

    官方演示: https://dwq.im 本地演示版本: https://www.weidro.cn 来源: https://dwq.im

    AlexTao
  • display:none和visibility:hidden的区别

    pitaojin
  • 电子规范管理系统(2)

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hotqin888/article/det...

    hotqin888
  • 揭秘Pokémon Go背后的故事:这款游戏不过是AR能做到的冰山一角

    大数据文摘
  • 每天一道 python 面试题 - Python中的元类(metaclass)

    虽然在Python中您可以对元类使用任意可调用对象(例如Jerub演示),但是更好的方法是使其成为实际的类。type是Python中常见的元类。type它本身是...

    公众号---人生代码
  • 聊聊pg jdbc的queryTimeout及next方法

    本文主要介绍一下pg jdbc statement的queryTimeout及resultSet的next方法

    codecraft

扫码关注云+社区

领取腾讯云代金券