5 爬虫 Scrapy框架 Redis数据库 手动请求发送

爬虫

1 Scrapy框架

1.1 介绍

Scrapy框架是基于异步爬虫的应用框架,用于高性能数据解析,高性能持久化存储,全站数据爬取,增量式爬虫和分布式爬虫等。

1.2 环境安装

Windows

1. pip install wheel
2. 下载twisted 
   http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
3. 进入twisted目录下,执行 pip3 install twisted文件名
   例如 pip install Twisted-20.3.0-cp38-cp38-win_amd64.whl
4. pip install pywin32
5. pip install scrapy

Linux

pip install scrapy

说明
twisted插件是scrapy框架实现异步操作的第三方组件。

1.3 基本使用
操作命令
创建项目scrapy startproject xxx (爬虫项目名)
创建爬虫文件scrapy genspider xxx (爬虫名) xxx.com (爬取域)
生成文件scrapy crawl xxx -o xxx.json (生成某种类型的文件)
运行爬虫scrapy crawl xxx (爬虫名)
列出所有爬虫scrapy list
获得配置信息scrapy settings [options]
1.3.1 创建工程
scrapy startproject DemoScrapyProject
1.3.2 工程目录
DemoScrapyProject
│  scrapy.cfg
│
└─DemoScrapyProject
    │  items.py
    │  middlewares.py
    │  pipelines.py
    │  settings.py
    │  __init__.py
    │
    ├─spiders
    │  │  __init__.py
    │  │
    │  └─__pycache__
    └─__pycache__

在工程目录中的项目名文件夹下,有一个名为spiders的文件夹,相当于爬虫包。
spiders爬虫文件夹中需要至少创建一个爬虫文件。

1.3.3 创建爬虫文件
cd DemoScrapyProject
scrapy genspider test www.test.com 

在项目目录下执行创建命令,会自动在spiders爬虫文件夹中创建名为test的爬虫文件。

import scrapy

class TestSpider(scrapy.Spider):
    name = 'test'
    allowed_domains = ['www.test.com']
    start_urls = ['http://www.test.com/']

    def parse(self, response):
        pass
类属性说明
name爬虫文件名称,是这个爬虫文件的唯一标识,不能重复。
allowed_domains允许域名,只爬取该限定域名下的网页。
start_urls起始url列表,用于存储即将发起请求的目标url。
parse用于数据解析,参数response是自动传入的响应对象。
import scrapy

class TestSpider(scrapy.Spider):
    name = 'test'
    allowed_domains = ['www.test.com']
    start_urls = ['https://www.sougou.com/', 'https://www.baidu.com/']

    def parse(self, response):
        pass
1.3.4 修改配置文件

settings.py

  1. 修改日志输出级别
LOG_LEVEL = 'ERROR'
  1. 不需要服从robots协议
ROBOTSTXT_OBEY = False
  1. UA伪装
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
1.3.5 执行工程
scrapy crawl test
1.3.6 数据解析

目标:爬取糗事百科中的段子。
方式:使用xpath定位标签,再提取数据。

提取数据的两种方式:
extract() 用于取Selector对象中的data;
extract_first() 返回字符串,取列表中的第一个Selector对象中的data。

selector_obj.extract()  用于取出selector_obj中的data,返回字符串;
[selector_obj1, selector_obj2...].extract()  用于取出列表中每个selector_obj中的data,返回列表。

[selector_obj1, selector_obj2...].extract_first() 用于取出列表中第一个元素selector_obj1中的data,返回字符串。
def parse(self, response):
    div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
    for each_div in div_list:
        # 返回的是Selector对象
        # 提取方式1:使用extract()
        author_selector_list1 = each_div.xpath('./div[1]/a[2]/h2/text()')  # [<Selector xpath='./div[1]/a[2]/h2/text()' data='\n吃了两碗又盛\n'>]
        author_list1 = author_selector_list1.extract()  # ['\n吃了两碗又盛\n']
		author_str1 = author_list1[0]  # '吃了两碗又盛'
		
        author_selector_obj2 = each_div.xpath('./div[1]/a[2]/h2/text()')[0]  # <Selector xpath='./div[1]/a[2]/h2/text()' data='\n吃了两碗又盛\n'>
        author_str2 = author_selector_obj2.extract()  # '吃了两碗又盛'
		
		# 提取方式2:使用extract_first()
        author_str2 = each_div.xpath('./div[1]/a[2]/h2/text()').extract_first()
        print(author_str2)
import scrapy

class TestSpider(scrapy.Spider):
    name = 'test'
    # allowed_domains = ['www.test.com']
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
        div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
        for each_div in div_list:
            author_str = each_div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            content_list = each_div.xpath('./a/div/span//text()').extract()
            content_str = ''.join(content_list)
1.3.7 持久化存储

有两种方式:

  1. 基于终端指令的持久化存储;
  2. 基于管道的持久化存储。
1.3.7.1 基于终端指令的持久化存储

局限性:

  1. 只能对parse方法的返回值进行持久化存储;
  2. 只能存储到指定类型的文件中,包括:json,jsonlines,jl,csv,xml,marshal,pickle;
  3. 不支持将数据写入到数据库中。
import scrapy

class TestSpider(scrapy.Spider):
    name = 'test'
    # allowed_domains = ['www.test.com']
    start_urls = ['https://www.qiushibaike.com/text/']
    
    def parse(self, response):
    	result_list = []
        div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
        for each_div in div_list:
            author_str = each_div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            content_list = each_div.xpath('./a/div/span//text()').extract()
            content_str = ''.join(content_list)
            temp_dict = {
				'author': author_str,
				'content': content_str
			}
            result_list.append(temp_dict)
        return result_list

终端指令

scrapy crawl test -o result.csv
1.3.7.2 基于管道的持久化存储

步骤:

  1. 进行数据解析;
  2. 在items.py中定义相关的属性,且属性个数需要与解析出的字段个数一致;
  3. 将解析出来的数据存储到Item类型的对象中;
  4. 将Item对象提交给管道;
  5. 在管道中接收Item对象,将该Item对象中存储的数据做任意形式的持久化存储;
  6. 在配置文件中开启管道机制。

步骤1 进行数据解析。

test.py

import scrapy

class TestSpider(scrapy.Spider):
    name = 'test'
    # allowed_domains = ['www.test.com']
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
        div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
        for each_div in div_list:
            author_str = each_div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            content_list = each_div.xpath('./a/div/span//text()').extract()
            content_str = ''.join(content_list)

步骤2 在items.py中定义相关的属性。

items.py

import scrapy

class DemoscrapyprojectItem(scrapy.Item):
    author = scrapy.Field()
    content = scrapy.Field()

步骤3 将解析出来的数据存储到Item类型的对象中。

test.py

import scrapy
from DemoScrapyProject.items import DemoscrapyprojectItem

class TestSpider(scrapy.Spider):
    name = 'test'
    # allowed_domains = ['www.test.com']
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
        div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
        for each_div in div_list:
            author_str = each_div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            content_list = each_div.xpath('./a/div/span//text()').extract()
            content_str = ''.join(content_list)
            
			# 实例化Item对象
            item_obj = DemoscrapyprojectItem()
            item_obj['author'] = author_str
            item_obj['content'] = content_str

步骤4 将Item对象提交给管道。

test.py

import scrapy
from DemoScrapyProject.items import DemoscrapyprojectItem

class TestSpider(scrapy.Spider):
    name = 'test'
    # allowed_domains = ['www.test.com']
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
        div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
        for each_div in div_list:
            author_str = each_div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            content_list = each_div.xpath('./a/div/span//text()').extract()
            content_str = ''.join(content_list)

            # 实例化Item对象
            item_obj = DemoscrapyprojectItem()
            item_obj['author'] = author_str
            item_obj['content'] = content_str

            # 将Item对象提交给管道
            yield item_obj

步骤5 在管道中接收Item对象,持久化存储数据。

pipelines.py

class DemoscrapyprojectPipeline:
    fp = None

    # 重写父类方法,用于打开文件。
    # 在整个工程的执行过程中该方法只会在爬虫开始时被执行一次。
    def open_spider(self, spider):
        self.fp = open('./result.txt', 'w', encoding='utf-8')

    def process_item(self, item, spider):
        '''
        用于接收Item对象,该方法调用的次数取决于爬虫文件向管道提交Item对象的次数。
        :param item: 接收到的Item对象
        :param spider: 爬虫类实例化的对象
        :return:
        '''
        author = item['author']
        content = item['content']
        self.fp.write(author)
        self.fp.write(content)
        return item

    # 重写父类方法,用于关闭文件。
    # 在整个工程的执行过程中该方法只会在爬虫结束时被执行一次。
    def close_spider(self, spider):
        self.fp.close()

方法中的参数spider指test.py中爬虫类实例化的对象,用于在管道文件中访问爬虫类的数据。

步骤6 在配置文件中开启管道机制。

settings.py

ITEM_PIPELINES = {
   'DemoScrapyProject.pipelines.DemoscrapyprojectPipeline': 300,
}
1.3.7.3 管道细节分析
  1. 优先级
    数字300表示管道的优先级,数值越小,则管道的优先级越高。
    管道的优先级越高,则表示该管道会被优先执行。

settings.py

ITEM_PIPELINES = {
   'DemoScrapyProject.pipelines.DemoscrapyprojectPipeline': 300,
}
  1. 多个管道类
    多个管道类一般用于数据备份,每一个管道类表示将数据存储到一种形式的载体中。
    如果需要将数据同时存储到MySQL和Redis中,则需要两个管道类来实现。

  2. return item
    爬虫文件只会将Item对象提交给优先级最高的管道。
    管道类的方法process_item中的return item可以将item对象交给下一个即将被执行的管道类。

例如封装一个管道类,将数据存储到MySQL数据库中。

pipelines.py

import pymysql

class MySQLPipeline:
    conn = None  # 数据库连接对象
    cursor = None  # 游标对象

    def open_spider(self, spider):
        self.conn = pymysql.Connect(
            host='127.0.0.1',
            port=3306,
            user='root',
            password='123456',
            db='spider_db',
            charset='utf8'
        )
        self.cursor = self.conn.cursor()

    def process_item(self, item, spider):
        author = item['author']
        content = item['content']
        sql = 'insert into qiushi values ("%s", "%s")' % (author, content)
        try:
            self.cursor.execute(sql)
            self.conn.commit()
        except Exception as e:
            print(e)
            self.conn.rollback()
            
        return item  # 用于向下一个管道类传递item对象。

    def close_spider(self, spider):
        self.cursor.close()
        self.conn.close()

在配置文件中注册这个管道类

settings.py

ITEM_PIPELINES = {
   'DemoScrapyProject.pipelines.DemoscrapyprojectPipeline': 300,
   'DemoScrapyProject.pipelines.MySQLPipeline': 301,
}

2 Redis数据库简介

2.1 介绍

Redis是一个非关系型数据库。

启动服务端 redis-server
启动客户端 redis-cli
2.2 基本存储单元
  1. set集合
  2. list列表
2.2.1 set集合
  1. 插入数据
    语法:sadd 集合名 值
    注意:集合不能存储重复数据。
    将字符串Ben和Elliot存储到集合name_set中。
127.0.0.1:6379> sadd name_set Ben
(integer) 1

127.0.0.1:6379> sadd name_set Elliot
(integer) 1

127.0.0.1:6379> sadd name_set Ben
(integer) 0
  1. 查看数据
    语法:smembers 集合名
    查看集合name_set中的数据。
127.0.0.1:6379> smembers name_set
1) "Elliot"
2) "Ben"
2.2.2 list列表
  1. 插入数据
    语法:lpush 列表名 值
    列表允许存储重复数据。
    将字符串Ben和Elliot存储到列表name_list中。
127.0.0.1:6379> lpush name_list Ben
(integer) 1

127.0.0.1:6379> lpush name_list Elliot
(integer) 2

127.0.0.1:6379> lpush name_list Ben
(integer) 3
  1. 查看列表长度
    语法:llen 列表名
    查看列表name_list的长度。
127.0.0.1:6379> llen name_list
(integer) 3
  1. 查看数据
    语法:lrange 列表名 起始下标 终止下标
127.0.0.1:6379> lrange name_list 0 1
1) "Ben"
2) "Elliot"

127.0.0.1:6379> lrange name_list 0 -1
1) "Ben"
2) "Elliot"
3) "Ben"

127.0.0.1:6379> lrange name_list 1 1
1) "Elliot"
2.2.3 通用命令
  1. 查看所有数据
127.0.0.1:6379> keys *
1) "name_list"
2) "name_set"
  1. 删除所有数据
127.0.0.1:6379> flushall
OK
2.3 Scrapy与Redis配合

安装redis模块,需要指定版本为2.10.6。
其它版本的redis模块无法直接向Redis数据库的列表中存入字典类型数据。

pip install redis-2.10.6
2.3.1 封装Redis的管道类

pipelines.py

import redis

class RedisPipeline:
    conn = None

    def open_spider(self, spider):
        self.conn = redis.Redis(
            host='127.0.0.1',
            port=6379
        )

    def process_item(self, item, spider):
        self.conn.lpush('qiushiData', item)
        return item
2.3.2 在配置文件中注册管道类

settings.py

ITEM_PIPELINES = {
   'DemoScrapyProject.pipelines.DemoscrapyprojectPipeline': 300,
   'DemoScrapyProject.pipelines.MySQLPipeline': 301,
   'DemoScrapyProject.pipelines.RedisPipeline': 302,
}

3 全站数据爬取

目标:对所有页码的数据进行爬取并存储。

3.1 爬取第一页数据
>>> scrapy startproject duanziProject
>>> cd duanziProject
>>> scrapy genspider duanzi www.duanziwang.com/

settings.py

BOT_NAME = 'duanziProject'

SPIDER_MODULES = ['duanziProject.spiders']
NEWSPIDER_MODULE = 'duanziProject.spiders'

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'

ROBOTSTXT_OBEY = False

LOG_LEVEL = 'ERROR'

ITEM_PIPELINES = {
   'duanziProject.pipelines.DuanziprojectPipeline': 300,
}

items.py

import scrapy

class DuanziprojectItem(scrapy.Item):
    title = scrapy.Field()
    content = scrapy.Field()

duanzi.py

import scrapy
from duanziProject.items import DuanziprojectItem

class DuanziSpider(scrapy.Spider):
    name = 'duanzi'
    # allowed_domains = ['www.duanziwang.com/']
    start_urls = ['https://duanziwang.com/category/一句话段子/1/']

    def parse(self, response):
        article_list = response.xpath('/html/body/section/div/div/main/article')
        for each_article in article_list:
            title_str = each_article.xpath('./div[1]/h1/a/text()').extract_first()
            content_str = each_article.xpath('./div[2]/p/text()').extract_first()
            item = DuanziprojectItem()
            item['title'] = title_str
            item['content'] = content_str
            yield item

piplines.py

class DuanziprojectPipeline:
    fp = None

    def open_spider(self, spider):
        self.fp = open('./duanzi.txt', 'w', encoding='utf-8')

    def process_item(self, item, spider):
        self.fp.write(item['title'])
        self.fp.write(item['content'])
        return item

    def close_spider(self, spider):
        self.fp.close()
3.2 手动请求发送

不使用Scrapy内置方式,通过代码的形式进行请求发送。

3.2.1 手动请求get发送
yield scrapy.Request(url, callback)

向指定url发送get请求,通过回调函数callback对响应数据进行解析。

duanzi.py

import scrapy
from duanziProject.items import DuanziprojectItem

class DuanziSpider(scrapy.Spider):
    name = 'duanzi'
    # allowed_domains = ['www.duanziwang.com/']
    start_urls = ['https://duanziwang.com/category/一句话段子/1/']
    url_model = 'https://duanziwang.com/category/一句话段子/%d/'
    page_num = 2

    def parse(self, response):
        article_list = response.xpath('/html/body/section/div/div/main/article')
        for each_article in article_list:
            title_str = each_article.xpath('./div[1]/h1/a/text()').extract_first()
            content_str = each_article.xpath('./div[2]/p/text()').extract_first()
            item = DuanziprojectItem()
            item['title'] = title_str
            item['content'] = content_str
            yield item

        # 结束递归条件
        if self.page_num < 10:
            new_url = format(self.url_model % self.page_num)
            self.page_num += 1

            # 手动发送请求,并调用回调函数对请求到的数据进行解析。
            # 回调函数是parse,发送递归调用。
            yield scrapy.Request(url=new_url, callback=self.parse)
3.2.2 手动请求post发送
yield scrapy.FormRequest(url, formdata, callback)

参数formdata指post请求携带的请求参数,格式是字典。

3.2.3 父类方法start_requests

问题:如何向start_url列表中的每一个url发送post请求?
关键:重写父类方法start_requests
父类方法start_requests对start_url列表中的每一个url默认发送get请求。

duanzi.py

import scrapy
from duanziProject.items import DuanziprojectItem

class DuanziSpider(scrapy.Spider):
    name = 'duanzi'
    start_urls = ['https://duanziwang.com/category/一句话段子/1/']
	...
	
	# 父类方法的原始实现,请求方式是get。
	def start_requests(self):
		for url in start_urls:
			yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        ...

重写父类方法start_requests

duanzi.py

import scrapy
from duanziProject.items import DuanziprojectItem

class DuanziSpider(scrapy.Spider):
    name = 'duanzi'
    start_urls = ['https://duanziwang.com/category/一句话段子/1/']
	...
	
	# 重写父类方法,请求方式改为post。
	def start_requests(self):
		for url in start_urls:
			yield scrapy.FormRequest(url=url, callback=self.parse)

    def parse(self, response):
        ...
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值