Scrapy框架

Scrapy框架环境的安装

环境安装比较多,跟着流程走就ok

1. pip3 install wheel
2. 下载twisted 链接:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
3. 进入下载目录,在该目录下cmd后,pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl
4. pip3 install pywin32
5. pip3 install scrapy

Scrapy的使用

从创建项目开始

# 创建一个工程
scrapy startproject 自己的项目名称

# 进入到项目文件
cd 项目名称

# 创建爬虫文件
scrapy genspider 爬虫文件名字 www.xxx.com
# 这里的网址随便填写就行,可以自己更改

其他配置:

- settings.py
# 不遵从rbotes协议
ROBOTSTXT_OBEY = False

# 进行UA伪装
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3861.0 Safari/537.36 Edg/77.0.230.2'

# 指定日志等级
LOG_LEVEL = 'ERROR'

运行项目:

scrapy crawl spiderName(自己起的爬虫文件名字)

持久化存储

- 基于终端命令的持久化:

  - 前提:只可以将parse方法的返回值进行本地文件的持久化存储

  - 指令:scrapy crawl spiderName -o filepath

- 基于管道的持久化存储:

  - 编码流程:

    - 数据解析

    - 需要在item类中定义相关的属性(存储解析到的数据)

    - 将解析到的数据存储或者封装到一个item类型的对象中

    - 将item对象提交到管道中

    - 在管道中需要接收item,且将item对象中的数据进行任意形式的持久化操作(例如数据库)

    - 在配置文件中开启管道

 举例(校花网):

创建项目:

scrapy startproject xiaohua

进入项目,创建爬虫文件

cd xiaohua
scrapy genspider meinv www.xxx.com

目录结构如下:

# UA
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3861.0 Safari/537.36 Edg/77.0.230.2'

# robots
ROBOTSTXT_OBEY = False
# 日志
LOG_LEVEL = 'ERROR'

# 管道
ITEM_PIPELINES = {
    'xiaohua.pipelines.XiaohuaPipeline': 300,
    # 300表示的是优先级,数字越小优先级越高
}
settings中配置
# -*- coding: utf-8 -*-
import scrapy
from ..items import XiaohuaItem


class MeinvSpider(scrapy.Spider):
    name = 'meinv'
    # allowed_domains = ['www.xiaohua.com']
    start_urls = ['http://www.xiaohuar.com/2014.html']

    def parse(self, response):
        img_list = response.xpath('//*[@id="images"]/div')
        for img in img_list:
            # extract_first():从Selector对象中取出解析出的字符串数据
            no = img.xpath("./div/div/text()").extract_first()
            imgSrc = 'http://www.xiaohuar.com' + img.xpath('./div/a/img/@src').extract_first()
            name = img.xpath('./p/a/text()').extract_first()
            # 实例化一个item类型对象
            item = XiaohuaItem()
            # 将解析到的数据存入到该对象中
            item['name'] = name
            item['no'] = no
            item['imgSrc'] = imgSrc

            # 将item提交到管道
            yield item
meinv.py
import scrapy


class XiaohuaItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    no = scrapy.Field()
    name = scrapy.Field()
    imgSrc = scrapy.Field()
items.py
class XiaohuaPipeline(object):
    fp = None

    # 只会在开始爬虫的时候调用一次
    def open_spider(self, spider):
        print('开始爬虫.....')
        self.fp = open('./xiaohua.txt', 'w', encoding='utf-8')

    # item:就是从爬虫文件中接收到的item对象
    # process_item这个方法会调用几次 -- 多次
    def process_item(self, item, spider):
        no = item['no']
        name = item['name']
        imgSrc = item['imgSrc']
        self.fp.write(no + '\t' + name + '\t' + imgSrc + '\n')
        return item

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

写完代码,运行即可

scrapy crawl meinv

补充:

  - 关于scrapy的数据解析,和普通的xpath的不同之处是什么

    - scrapy是通过 response.xpath()解析,并且返回的列表中存储的是Selector对象,必须使用extract(),如果确定只有一个数据可以使用extract_first()

  - scrapy管道的细节处理

    - 一个管道类负责将数据存储到某一个载体或者平台中

    - 爬虫文件添加的item只会提交给第一个被执行的管道类

    - 在管道类的process_item中的renturn item表示的含义就是将当前管道类接收的item传递给下一个将被执行的管道类

  - 注意:爬虫类和管道类进行数据交互的形式

    - yield item:只可以交互item类型的对象

    - spider参数:可以交互任意形式的数据

糗事百科(基于管道分别存储到mysql 和 redis)

# -*- coding: utf-8 -*-
import scrapy
from ..items import QiubaiproItem


class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    # 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[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first()
            content = div.xpath('./a[1]/div/span//text()').extract()
            content = ''.join(content)
            print(author)
            item = QiubaiproItem()
            item['author'] = author
            item['content'] = content

            yield item
qiubai.py
import scrapy


class QiubaiproItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    author = scrapy.Field()
    content = scrapy.Field()
items.py
import pymysql
from redis import Redis


# 一个单独的管道类负责将数据存储到mysql
class QiubaiproPipeline(object):
    conn = None
    cursor = None

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

    def process_item(self, item, spider):
        self.cursor = self.conn.cursor()
        sql = 'insert into qiubai values ("%s","%s")' % (item['author'], item['content'])
        # 进行事物处理
        try:
            self.cursor.execute(sql)
            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()


class RedisPipeline(object):
    conn = None

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

    def process_item(self, item, spider):
        dic = {
            'author': item['author'],
            'content': item['content']
        }
        self.conn.lpush('qiuBaiData', dic)
pipelines.py

全站数据爬取

- 基于手动请求发送实现

- 手动请求发送:

  - yield scrapy.Request(url,callback)

  - yield scrapy.FormRequest(url,formdata,callback)

 注意点:

scrapy中的cookie是自动封装好的
scrapy主要是发get请求,也可以发post,因为post请求使用scrapy有点麻烦还不如requests
yield scrapy.Request(url,callback)  # scrapy发送get请求
yield scrapy.FormRequest(url,formdata,callback)  # scrapy发送post请求

# 是父类请求发送的封装,发的是get请求,如果需要发post请求就重写
def start_requests(self):
    for url in self.start_urls:
        yield scrapy.Request(url=url, callback=self.parse)

# 重写post的话也是这样(当然一般不使用scrapy发post请求,还不如requests)
def start_requests(self):
    for url in self.start_urls:
        yield scrapy.FormRequest(url=url, formdata={'post请求参数'}, callback=self.parse)

深度爬取

- 不在同一张页面的信息爬取就是在深度爬取

- 基于手动请求发送

- 请求传参:持久化存储,将不同的回调中解析的数据存储到同一个item对象。请求传参传递的就是item对象

  - 使用场景:如果使用scrapy爬取的数据没有在同一张页面中

  - 传递方式:将传递数据封装到meta字典中,meta传递给了callback

    - yield scrapy.Request(url,callback,meta)

  - 接收:

    - 在callback指定的回调函数中使用response进行接收:

      - item = response.meta['key']  # 自己指定的key

 案例:糗事百科的全站和深度爬取

# -*- coding: utf-8 -*-
import scrapy
from ..items import QiubaideepproItem


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

    url = 'https://www.qiushibaike.com/text/page/%d/'

    page = 2

    def parse(self, response):
        print(f'正在爬取第{self.page}页数据')
        # 解析糗事百科作者名称和对应内容
        div_list = response.xpath('//div[@id="content-left"]/div')
        for div in div_list:
            author = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first()
            # content = div.xpath('./a[1]/div/span//text()').extract()
            # content = ''.join(content)

            item = QiubaideepproItem()
            item['author'] = author

            detail_url = 'https://www.qiushibaike.com' + div.xpath('./a[1]/@href').extract_first()

            # 对详情页发请求
            yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})

        if self.page <= 5:
            # 对其他网页进行手动请求发送
            new_url = format(self.url % self.page)
            self.page += 1
            # 手动发请求(发生了递归)
            yield scrapy.Request(new_url, callback=self.parse)

    # 解析详情页
    def parse_detail(self, response):
        item = response.meta['item']
        content_desc = response.xpath('//*[@id="single-next-link"]/div//text()').extract()
        content_desc = ''.join(content_desc)
        item['content_desc'] = content_desc
        yield item
qiubai.py
import scrapy


class QiubaideepproItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    author = scrapy.Field()
    content_desc = scrapy.Field()
items.py
class QiubaideepproPipeline(object):
    def process_item(self, item, spider):
        print(item)
        return item
pipelines.py

这里没有持久化存储,打印就可以看到结果。

其实这样的全站爬取不如CrawlSpider。

五大核心组件

- 引擎(Scrapy)

  用来处理整个系统的数据流处理,触发事物(框架核心)

- 调度器(Scheduler)  -- 包括过滤器,队列

  用来接收引擎发过来的请求,压入队列中,并且在引擎再次请求的时候返回,可以想象成一个url(抓取网页的网址或者说是链接)的优先队列,由它来决定下一个抓取的网址是什么,同时去除重复网址

- 下载器(Downloader)

  用于下载网页内容,并将网页内容返回给spider(Scrapy下载器是建立在twisted这个高效的异步模型上的)

- 爬虫(Spiders)

  爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面

- 项目管道(Pipeline)

  负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。

如何提升scrapy爬取数据的效率

增加并发:
    默认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

 

posted @ 2019-08-11 16:54  blog_wu  阅读(117)  评论(0编辑  收藏  举报