爬虫之scrapy框架

1.配置环境:

1.介绍:是一个具有很多功能且具有很强通用性的一个项目模板
2.Linux: 直接 pip install scrapy
3.windows:
    1.pip install wheel
    2.下载twisted https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
    3.进入到下载的目录 pip install Twisted-19.2.0-cp36-cp36m-win_amd64
    4.pip install pywin32
    5.pip install scrapy

2.命令:

1.创建项目: scrapy startproject 项目名称
2.创建爬虫的应用程序;
    进入项目的目录
    scrapy genspider 应用的名称 爬取网页的其实url 
3.编写爬虫文件,在第二步执行完的时候就会在项目的spiders中生成一个应用名的py爬虫文件
4.修改settings.py配置文件的相关配置
5.执行爬虫程序在cmd中
	scrapy crawl 应用的名称

3.基础使用:

1.创建项目: scrapy startproject 项目名称
project_name/
   scrapy.cfg:
   project_name/
       __init__.py
       items.py
       pipelines.py
       settings.py
       spiders/
           __init__.py

scrapy.cfg   项目的主配置信息。(真正爬虫相关的配置信息在settings.py文件中)
items.py     设置数据存储模板,用于结构化数据,如:Django的Model
pipelines    数据持久化处理
settings.py  配置文件,如:递归的层数、并发数,延迟下载等
spiders      爬虫目录,如:创建文件,编写爬虫解析规则

 

2.创建爬虫的应用程序;
    进入项目的目录
    scrapy genspider 应用的名称 爬取网页的其实url 

  

3.编写爬虫文件,在第二步执行完的时候就会在项目的spiders中生成一个应用名的py爬虫文件

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

class QiubaiSpider(scrapy.Spider):
    name = 'qiubai' #应用名称
    #允许爬取的域名(如果遇到非该域名的url则爬取不到数据)
    allowed_domains = ['https://www.qiushibaike.com/']
    #起始爬取的url
    start_urls = ['https://www.qiushibaike.com/']

     #访问起始URL并获取结果后的回调函数,该函数的response参数就是向起始的url发送请求后,获取的响应对象.该函数返回值必须为可迭代对象或者NUll 
     def parse(self, response):
        print(response.text) #获取字符串类型的响应内容
        print(response.body)#获取字节类型的相应内容

  

4.修改settings.py配置文件的相关配置
19行:USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' #伪装请求载体身份

22行:ROBOTSTXT_OBEY = False  #可以忽略或者不遵守robots协议

  

5.执行爬虫程序在cmd中
scrapy crawl 应用的名称

4.案例

例子: 爬取站长素材的图片 在scrapy项目和爬虫应用建好的情况下:
from urllib import request

import requests
import scrapy
import os
from lxml import etree


class ZzmeiziSpider(scrapy.Spider):
    name = 'zzmeizi'
    # allowed_domains = ['http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html']
    start_urls = ['http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html']

    def parse(self, response):
        # print(response.text)  # 获取字符串类型的响应内容
        # print(response.body)  # 获取字节类型的相应内容
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
        }
        tree = etree.HTML(response.body)
        ret_list = tree.xpath('//*[@id="container"]//div/div/a/@href')
        print(ret_list)
        if not os.path.exists('./zzmeizi'):
            os.mkdir('./zzmeizi')

        for div in ret_list:
            page_text = requests.get(url=div, headers=headers).content
            tree = etree.HTML(page_text)
            img_name = tree.xpath('//div[@class="imga"]/a/@title')[0]
            img_url = tree.xpath('//div[@class="imga"]/a/@href')[0]
            request.urlretrieve(img_url, './zzmeizi/%s.jpg' % (img_name)) // 访问并保存 

5.使用管道进行持久化存储:

全站数据的爬取:手动请求的发送
scrapy默认可以进行cookie的携带和处理

处理请求传参:
    - 使用场景:爬取解析的数据没有存在于同一张页面中
    - meta参数

持久化存储的实现流程(基于管道):
    - 数据解析
    - 封装item类(将字段先在items文件中封装一下)
    - 实例化item类型的对象
    - 将解析到的数据依次存储封装到item类型的对象中
    - 将item提交给管道
    - 在管道中实现io操作
    - 在settings文件中开启管道
    将一份数据存储到不同的平台中
    	- 管道文件中一个管道类负责将item存储到某一个平台中
    	- 配置文件中设定管道类的优先级
    	- process_item 方法中return item 的操作将item传递给下一个即将被执行的管道类
import scrapy
class XioahuaproItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()
    img_url = scrapy.Field()
封装item
class MysqlPipeline(object):
    conn = None
    cursor = None
    def open_spider(self, spider):
        #解决数据库字段无法存储中文处理:alter table tableName convert to charset utf8;
        self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123',db='test')
        print(self.conn)
    def process_item(self, item, spider):
        self.cursor = self.conn.cursor()
        try:
            self.cursor.execute('insert into xiahua values ("%s","%s")'%(item['name'],item['img_url']))
            self.conn.commit()
        except Exception as e:
            print(e)
            self.conn.rollback()
        return item
    def close_spider(self, spider):
        self.cursor.close()
        self.conn.close()
将数据持久化到数据库
import scrapy
class PostdemoSpider(scrapy.Spider):
    name = 'postDemo'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://fanyi.baidu.com/sug']

    def start_requests(self):
        data = {
            'kw':'dog'
        }
        for url in self.start_urls:
            yield scrapy.FormRequest(url=url,callback=self.parse,formdata=data)

    def parse(self, response):
        print(response.text)
使用scrapy框架完成post请求
当进行深层爬取时需要进行数据的传递;
class MovieSpider(scrapy.Spider):
    name = 'movie'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567tv.tv/index.php/vod/show/id/9.html']
    #接收一个请求传递过来的数据
    def detail_parse(self,response):
        item = response.meta['item']
        desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
        item['desc'] = desc

        yield item
    def parse(self, response):
        li_list = response.xpath('//div[@class="stui-pannel_bd"]/ul/li')
        for li in li_list:
            name = li.xpath('.//h4[@class="title text-overflow"]/a/text()').extract_first()
            detail_url = 'https://www.4567tv.tv'+li.xpath('.//h4[@class="title text-overflow"]/a/@href').extract_first()
            item = MovieproItem()
            item['name'] = name
            #meta是一个字典,字典中所有的键值对都可以传递给指定好的回调函数
            yield scrapy.Request(url=detail_url,callback=self.detail_parse,meta={'item':item})
meta传递参数

6.增加并发

增加并发:
默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。

降低日志级别:
    在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘INFO’

禁止cookie:
    如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False

禁止重试:
    对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False

减少下载超时:
    如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s

  

7.中间件

下载中间件

​	- 作用:批量拦截请求个响应

​	- 拦截请求:

​		1. 篡改请求头信息(User-Agent):

​		2.处理异常的请求

​		3.对响应数据进行修改

1.在scrapy中如何给所有的请求对象尽可能多的设置不一样的请求载体身份表示

- UA池, process_request(request) 中间件中处理

2.scrapy 中如何给发生异常请求设置代理IP

- ip池, process_exception(request,response,spider):request.meta['proxy' ] = 'http://ip:prot'
- 讲异常的请求拦截到之后,通过代理ip相关的操作,就可以将异常的请求变为非异常的请求,对该请求重新发送: return request

3.简述下载中间件的作用及其最重要三个方法的作用
- process_request:拦截所有非异常的请求
- process_response:拦截所有的响应对象
- process_exception:拦截发生异常的请求对象,需要对异常的请求对象进行相关处理,让其变成
    正常的请求对象,然后通过return request 对该请求进行重新发送

 4.简述scrapy中什么时候需要使用selenium及其scrapy应用selenium的编码流程

- 实例化浏览器对象(一次):spider的init方法
- 需要编写浏览器自动化的操作(中间件的process_response)
- 关闭浏览器(spider的closed方法中进行关闭操作)

 

8.crawlSpider

- crawlSpider全站数据爬取
    - crawlSpider就是spider一个子类(派生)
    - crawlSpider具有的机制:
        - 连接提取器
        - 规则解析器
    - 创建爬虫文件:
    - 深度爬取
    - 命令
    	- scrapy genspider -t crawl 应用名 url地址

  

# 爬虫文件 
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from bossPro.items import DetailItem,FirstItem
#爬取的是岗位名称(首页)和岗位描述(详情页)
class BossSpider(CrawlSpider):
    name = 'boss'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.zhipin.com/c101010100/?query=python%E5%BC%80%E5%8F%91&page=1&ka=page-prev']
    #获取所有的页码连接
    link = LinkExtractor(allow=r'page=d+')
    link_detail = LinkExtractor(allow=r'/job_detail/.*?html')
    #/job_detail/f2a47b2f40c53bd41XJ93Nm_GVQ~.html
    #/job_detail/47dc9803e93701581XN80ty7GFI~.html
    rules = (
        Rule(link, callback='parse_item', follow=True),
        Rule(link_detail, callback='parse_detail'),
    )
    #将页码连接对应的页面数据中的岗位名称进行解析
    def parse_item(self, response):
        li_list = response.xpath('//div[@class="job-list"]/ul/li')
        for li in li_list:
            item = FirstItem()
            job_title = li.xpath('.//div[@class="job-title"]/text()').extract_first()
            item['job_title'] = job_title
            # print(job_title)

            yield item
    def parse_detail(self,response):
        job_desc = response.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()').extract()
        item = DetailItem()
        job_desc = ''.join(job_desc)
        item['job_desc'] = job_desc

        yield item
案例 爬取BOSS直聘 爬虫文件
# item 文件
import scrapy
class DetailItem(scrapy.Item):
    # define the fields for your item here like:
    job_desc = scrapy.Field()
class FirstItem(scrapy.Item):
    # define the fields for your item here like:
    job_title = scrapy.Field()
item文件
# 管道 持久化文件
class BossproPipeline(object):
    f1,f2 = None,None
    def open_spider(self,spider):
        self.f1 = open('a.txt','w',encoding='utf-8')
        self.f2 = open('b.txt', 'w', encoding='utf-8')
    def process_item(self, item, spider):

        #item在同一时刻只可以接收到某一个指定item对象
        if item.__class__.__name__ == 'FirstItem':
            job_title = item['job_title']
            self.f1.write(job_title+'
')
        else:
            job_desc = item['job_desc']
            self.f2.write(job_desc)
        return item
# settings 文件 要加UA 并且把ROBOTSTXT_OBEY 改为False
持久化文件

9.分布式爬虫

概念:使用多台机器组成一个分布式的机群,在机群中运行同一组程序,进行联合数据的爬取。
原生的scrapy是不可以实现分布式:
        - 原生的scrapy中的调度器不可以被共享
        - 原生的scrapy的管道不可以被共享
    - 如果实现分布式就必须使用scrapy-redis(模块)
        - 可以给原生的scrapy提供可以被共享的管道和调度器
        - pip install scrapy_redis
搭建流程:
        - 创建工程 
        	- scrapy startproject 项目名称
        - 爬虫文件
        	- scrapy genspider -t crawl 应用名 url地址
        - 修改爬虫文件:
            - 导报:from scrapy_redis.spiders import RedisCrawlSpider
            - 将当前爬虫类的父类进行修改RedisCrawlSpider
            - allowed_domains,start_url删除,添加一个新属性redis_key(调度器队列的名称)
            - 数据解析,将解析的数据封装到item中然后向管道提交
        - 配置文件的编写:
            - 指定管道:
                                ITEM_PIPELINES = {
                         'scrapy_redis.pipelines.RedisPipeline': 400
                        }
            - 指定调度器:
                # 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
                DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
                # 使用scrapy-redis组件自己的调度器
                SCHEDULER = "scrapy_redis.scheduler.Scheduler"
                # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
                SCHEDULER_PERSIST = True
            - 指定具体的redis:
                REDIS_HOST = 'redis服务的ip地址'
                REDIS_PORT = 6379
                REDIS_ENCODING = ‘utf-8’
                REDIS_PARAMS = {‘password’:’123456’}
            - 开启redis服务(携带redis的配置文件:redis-server ./redis.windows.conf),和客户端:
                - 对redis的配置文件进行适当的配置:
                        - #bind 127.0.0.1
                        - protected-mode no
                 - 开启
             - 启动程序:scrapy runspider xxx.py
             - 向调度器队列中扔入一个起始的url(redis的客户端):lpush xxx www.xxx.com
                - xxx表示的就是redis_key的属性值
- 问题:
	- 可能会出现,只要一台电脑在工作,而其他电脑没有工作,把setting文件里的
	CONCURRENT_REQUESTS = 32 改的小一点

  

# 爬虫文件
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_redis.spiders import RedisCrawlSpider
from fbsPro.items import FbsproItem
class TestSpider(RedisCrawlSpider):
    name = 'test'
    # allowed_domains = ['www.xxx.com']
    # start_urls = ['http://www.xxx.com/']
    #调度器队列的名称
    redis_key = 'dongguan'
    rules = (
        Rule(LinkExtractor(allow=r'type=4&page=d+'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        a_list = response.xpath('//a[@class="news14"]')
        for a in a_list:
            item = FbsproItem()
            item['title']= a.xpath('./text()').extract_first()

            yield item
案例 爬虫文件
# item 文件
import scrapy
class FbsproItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
item文件
#settings 文件 添加一下配置
ITEM_PIPELINES = {
   # 'fbsPro.pipelines.FbsproPipeline': 300,
    'scrapy_redis.pipelines.RedisPipeline': 400 # 数字表示权重
}
# 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
SCHEDULER_PERSIST = True

REDIS_HOST = '***.***.**.***' # 连接redis 数据库的地址
REDIS_PORT = 6379   #连接数据库的端口
Settings文件
# 管道文件
由于使用了 分布式爬虫,数据存储在redis数据库中,如果要做持久化存储的话就必须将数据从redis数据库中读取出来再进行存储
管道文件

10.增量式爬虫

- 概念:用来《检测》网站数据更新的情况。只会爬取网站中更新出来的新数据。
- 核心:去重
# 爬虫文件
import scrapy
from qiubaiPro.items import QiubaiproItem
import hashlib
from redis import Redis
class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    conn = Redis(host='127.0.0.1',port=6379)
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
        div_list = response.xpath('//div[@id="content-left"]/div')
        for div in div_list:
            #数据指纹:爬取到一条数据的唯一标识
            author = div.xpath('./div/a[2]/h2/text() | ./div/span[2]/h2/text()').extract_first()
            content = div.xpath('./a/div/span//text()').extract()
            content = ''.join(content)

            item = QiubaiproItem()
            item['author'] = author
            item['content'] = content

            #数据指纹的创建
            data = author+content
            hash_key = hashlib.sha256(data.encode()).hexdigest()
            ex = self.conn.sadd('hash_keys',hash_key)
            if ex == 1:
                print('有新数据更新......')
                yield item
            else:
                print('无数据更新!')
案例 爬虫文件
#  items 文件
import scrapy
class QiubaiproItem(scrapy.Item):
    # define the fields for your item here like:
    author = scrapy.Field()
    content = scrapy.Field()
item文件
# settings 文件 
配置好 UA 并且把ROBOTSTXT_OBEY 改为False
Settings文件
原文地址:https://www.cnblogs.com/changtao/p/10821430.html