python爬虫六

其实这次的内容我是想放在上一篇的博文中的,但是上次犯懒了,就放在这里了奥。

基于mysql持久化操作:

将爬取数据写入文件这种情况还是少见的,因为文件无论是内存占用还是读写速度都存在一定的瑕疵,所以我们更多的是操作数据库。因为items对象已经准备好了所以我们只需要进行pipeline管道更改就可以了

import pymysql
class QiubaiproPipelineByMysql(object):

    conn = None  #mysql的连接对象声明
    cursor = None#mysql游标对象声明
    def open_spider(self,spider):
        print('开始爬虫')
        #链接数据库
        self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456',db='qiubai')
    #编写向数据库中存储数据的相关代码
    def process_item(self, item, spider):
        #1.链接数据库
        #2.执行sql语句
        sql = 'insert into qiubai values("%s","%s")'%(item['author'],item['content'])
        self.cursor = self.conn.cursor()
        #执行事务
        try:
            self.cursor.execute(sql)
            self.conn.commit()
        except Exception as e:
            print(e)
            self.conn.rollback()

        return item
    def close_spider(self,spider):
        print('爬虫结束')
        self.cursor.close()
        self.conn.close()

- settings.py

ITEM_PIPELINES = {
    'qiubaiPro.pipelines.QiubaiproPipelineByMysql': 300,
}
基于redis持久化操作:

有时我们指定的数据库类型不同,可能存在存储进redis数据库的操作,同样的我们只需要对管道进行更改

import redis

class QiubaiproPipelineByRedis(object):
    conn = None
    def open_spider(self,spider):
        print('开始爬虫')
        #创建链接对象
        self.conn = redis.Redis(host='127.0.0.1',port=6379)
    def process_item(self, item, spider):
        dict = {
            'author':item['author'],
            'content':item['content']
        }
        #写入redis中
        self.conn.lpush('data', dict)
        return item

当我们需要同时进行两种不一样的持久化存储的时候我们怎么做?

我们只要在管道内实现定义好类,在配置文件中进行设置就可以了

ITEM_PIPELINES = {
   'doublekill.pipelines.DoublekillPipeline': 300,
    'doublekill.pipelines.DoublekillPipeline_db': 200,
}
爬取多页数据:

在之前我们介绍了scrapy的简单使用和持久化存储,但都是针对单页数据而言的,那么我们需要的数据十分不在多页的时候呢?

这里有两种方法,我个人更倾向于第二种。

第一种:递归爬取,在回调函数中设置,如果当前总页数没有爬取完整那么接下来再次回传给request重新发送请求

import scrapy
from qiushibaike.items import QiushibaikeItem
# scrapy.http import Request
class QiushiSpider(scrapy.Spider):
    name = 'qiushi'
    allowed_domains = ['www.qiushibaike.com']
    start_urls = ['https://www.qiushibaike.com/text/']

    #爬取多页
    pageNum = 1 #起始页码
    url = 'https://www.qiushibaike.com/text/page/%s/' #每页的url

    def parse(self, response):
        div_list=response.xpath('//*[@id="content-left"]/div')
        for div in div_list:
            #//*[@id="qiushi_tag_120996995"]/div[1]/a[2]/h2
            author=div.xpath('.//div[@class="author clearfix"]//h2/text()').extract_first()
            author=author.strip('\n')
            content=div.xpath('.//div[@class="content"]/span/text()').extract_first()
            content=content.strip('\n')
            item=QiushibaikeItem()
            item['author']=author
            item['content']=content

            yield item #提交item到管道进行持久化

         #爬取所有页码数据
        if self.pageNum <= 13: #一共爬取13页(共13页)
            self.pageNum += 1
            url = format(self.url % self.pageNum)

            #递归爬取数据:callback参数的值为回调函数(将url请求后,得到的相应数据继续进行parse解析),递归调用parse函数
            yield scrapy.Request(url=url,callback=self.parse)

  

第二种(个人较为推荐):

之前提到过,scrapy框架是一个高度封装的框架,我们可以重写其某些方法达到自定义功能的目的,例如重写starturl

import scrapy
from Duanzi.items import DuanziItem
class DuanziSpider(scrapy.Spider):
    name = 'duanzi'
    # allowed_domains = ['www.xxx.com']

    def start_requests(self):

        for i in range(1,6):
            url = "https://duanziwang.com/category/经典段子/%d/"%i
            yield scrapy.Request(
                url=url,
                callback=self.parse
            )


    def parse(self, response):

        article_list = response.xpath('/html/body/section/div/div/main/article')
        for article in article_list:
            title = article.xpath('./div[1]/h1/a/text()').extract_first()
            content = article.xpath('./div[2]/p/text()').extract_first()

            item = DuanziItem()
            item['title'] = title
            item['content'] = content
            yield item

  

上述过程我们都使用了scrapy.request为什么这个方法能够发送请求呢?这就要从scrapy结构说起了。

scrapy有五大核心组件,分别是下载器,spider,管道,调度器,引擎

  • 引擎(Scrapy) 用来处理整个系统的数据流处理, 触发事务(框架核心)

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

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

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

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

所以总的来说一次请求的过程大概是这样的

1 首先,我们最原始的起始url是在我们爬虫文件中的,通常情况系,起始的url只有一个,当我们的爬虫文件执行的时候,首先对起始url发送请求,将起始url封装成了请求对象,将请求对象传递给了引擎,引擎就收到了爬虫文件给它发送的封装了起始URL的请求对象。我们在爬虫文件中发送的请求并没有拿到响应(没有马上拿到响应),只有请求发送到服务器端,服务器端返回响应,才能拿到响应。

2 引擎拿到这个请求对象以后,又将请求对象发送给了调度器,队列接受到的请求都放到了队列当中,队列中可能存在多个请求对象,然后通过过滤器,去掉重复的请求

3 调度器将过滤后的请求对象发送给了引擎,

4 引擎将拿到的请求对象给了下载器

5 下载器拿到请求后将请求拿到互联网进行数据下载

6 互联网将下载好的数据发送给下载器,此时下载好的数据是封装在响应对象中的

7 下载器将响应对象发送给引擎,引擎接收到了响应对象,此时引擎中存储了从互联网中下载的数据。

8 最终,这个响应对象又由引擎给了spider(爬虫文件),由parse方法中的response对象来接收,然后再parse方法中进行解析数据,此时可能解析到新的url,然后再次发请求;也可能解析到相关的数据,然后将数据进行封装得到item,

9 spider将item发送给引擎

10 引擎将item发送给管道。

其中,在引擎和下载中间还有一个下载器中间件,spider和引擎中间有爬虫中间件,

下载器中间件

可以拦截请求和响应对象,请求和响应交互的时候一定会经过下载中间件,可以处理请求和响应。

爬虫中间件

拦截请求和响应,对请求和响应进行处理。

post请求:

我们需要通过重写start_request方法来进行post请求

def start_requests(self):
        #请求的url
        post_url = 'http://fanyi.baidu.com/sug'
        # post请求参数
        formdata = {
            'kw': 'wolf',
        }
        # 发送post请求
        yield scrapy.FormRequest(url=post_url, formdata=formdata, callback=self.parse)
scrapy日志等级设置

对于任何一个程序项目来说,日志都是关键的东西(尤其是运维,小伙伴一定要学着会看日志)。那么作为一个高度封装且极为好用的框架来说scrapy的日志等级该如何设置。

在scrapy的settings中是存在相关配置的

LOG_LEVEL = ‘指定日志信息种类’即可。

LOG_FILE = 'log.txt'则表示将日志信息写入到指定文件中进行存储。

请求传参:

我们有时候会需要根据第一个页面的数据来请求第二次,从而得到第一个页面和第二个页面的数据进行分析,那么这时就需要用到请求传参

import scrapy
from moviePro.items import MovieproItem

class MovieSpider(scrapy.Spider):
    name = 'movie'
    allowed_domains = ['www.id97.com']
    start_urls = ['http://www.id97.com/']

    def parse(self, response):
        div_list = response.xpath('//div[@class="col-xs-1-5 movie-item"]')

        for div in div_list:
            item = MovieproItem()
            item['name'] = div.xpath('.//h1/a/text()').extract_first()
            item['score'] = div.xpath('.//h1/em/text()').extract_first()
            #xpath(string(.))表示提取当前节点下所有子节点中的数据值(.)表示当前节点
            item['kind'] = div.xpath('.//div[@class="otherinfo"]').xpath('string(.)').extract_first()
            item['detail_url'] = div.xpath('./div/a/@href').extract_first()
            #请求二级详情页面,解析二级页面中的相应内容,通过meta参数进行Request的数据传递
            yield scrapy.Request(url=item['detail_url'],callback=self.parse_detail,meta={'item':item})

    def parse_detail(self,response):
        #通过response获取item
        item = response.meta['item']
        item['actor'] = response.xpath('//div[@class="row"]//table/tr[1]/a/text()').extract_first()
        item['time'] = response.xpath('//div[@class="row"]//table/tr[7]/td[2]/text()').extract_first()
        item['long'] = response.xpath('//div[@class="row"]//table/tr[8]/td[2]/text()').extract_first()
        #提交item到管道
        yield item

  items文件:

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy


class MovieproItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()
    score = scrapy.Field()
    time = scrapy.Field()
    long = scrapy.Field()
    actor = scrapy.Field()
    kind = scrapy.Field()
    detail_url = scrapy.Field()

    管道文件:

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

import json
class MovieproPipeline(object):
    def __init__(self):
        self.fp = open('data.txt','w')
    def process_item(self, item, spider):
        dic = dict(item)
        print(dic)
        json.dump(dic,self.fp,ensure_ascii=False)
        return item
    def close_spider(self,spider):
        self.fp.close()
提高scrapy效率

在某方面来说scarpy为了完美运行是对我们进行了一些限制的,所以当我们认为手动的打开了这些限制,我们就会获得更高的执行效率

增加并发:
默认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 @ 2020-10-10 10:33  TopJocker  阅读(130)  评论(0编辑  收藏  举报