您现在的位置是:网站首页 > 博客日记 >

爬虫五(Scrapy框架整体流程介绍、Scrapy解析数据爬取Cnblogs文章信息、Settings相关配置提高爬取效率、持久化方案保存到本地数据库、爬虫中间件)

作者:YXN-python 阅读量:122 发布日期:2022-03-10

一、Scrapy架构流程介绍

Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,使用它可以以快速、简单、可扩展的方式从网站中提取所需的数据。但目前Scrapy的用途十分广泛,可用于如数据挖掘、监测和自动化测试等领域,也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。

Scrapy 是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架。因此Scrapy使用了一种非阻塞(又名异步)的代码来实现并发。整体架构大致如下:

官网链接:https://docs.scrapy.org/en/latest/topics/architecture.html

官方原文解释:

The data flow in Scrapy is controlled by the execution engine, and goes like this:

1. The Engine gets the initial Requests to crawl from the Spider. (引擎从Spider获取要爬行的初始请求。)

2. The Engine schedules the Requests in the Scheduler and asks for the next Requests to crawl. (引擎在调度器中对请求进行调度,并要求对下一个请求进行爬取。)

3. The Scheduler returns the next Requests to the Engine. (调度器将下一个请求返回给引擎。)

4. The Engine sends the Requests to the Downloader, passing through the Downloader Middlewares (see process_request()). (引擎将请求发送给下载器,通过下载器中间件(请参阅process_request())。)

5. Once the page finishes downloading the Downloader generates a Response (with that page) and sends it to the Engine, passing through the Downloader Middlewares (see process_response()). (一旦页面完成下载,Downloader就会生成一个响应(使用该页面)并将其发送给引擎,通过Downloader中间件传递(请参阅process_response())。)

6. The Engine receives the Response from the Downloader and sends it to the Spider for processing, passing through the Spider Middleware (see process_spider_input()). (引擎从下载器接收响应,并通过Spider中间件将其发送给Spider进行处理(参见process_spider_input())。)

7. The Spider processes the Response and returns scraped items and new Requests (to follow) to the Engine, passing through the Spider Middleware (see process_spider_output()). (Spider处理响应,并通过Spider Middleware(参见process_spider_output())将抓取的项和新的请求返回给引擎。)

8. The Engine sends processed items to Item Pipelines, then send processed Requests to the Scheduler and asks for possible next Requests to crawl. (引擎将处理过的项目发送到项目管道,然后将处理过的请求发送到调度器,并请求抓取可能的下一个请求。)

9. The process repeats (from step 1) until there are no more requests from the Scheduler. (该过程重复(从步骤1开始),直到没有来自Scheduler的更多请求。)

大致解释意思为:

引擎(EGINE)
 引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。有关详细信息,请参见上面的数据流部分。
 
 调度器(SCHEDULER)
 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
 
 下载器(DOWLOADER)
 用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的
 
 爬虫(SPIDERS)--->在这里写代码
 SPIDERS是开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求
 
 项目管道(ITEM PIPLINES)
 在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
 
 下载器中间件(Downloader Middlewares)
 位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response,
 你可用该中间件做以下几件事:设置请求头,设置cookie,使用代理,集成selenium
 
 爬虫中间件(Spider Middlewares)
 位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)

二、Scrapy解析数据(爬取Cnblogs文章信息)

import scrapy
 from bs4 import BeautifulSoup
 
 
 class CnblogsSpider(scrapy.Spider):
     name = 'cnblogs'
     allowed_domains = ['www.cnblogs.com']
     start_urls = ['http://www.cnblogs.com/']
 
     """
        response类似于requests模块的response对象 print(response.text就能拿到结果)
        以下总共展示三种方法: 1.使用Bs4 2.使用css解析 3.xpath选择器
    """
 
     def parse(self, response):
         '''方式1.使用Bs4'''
         soup = BeautifulSoup(response.text, 'lxml')
         article_list = soup.find_all(class_='post-item')
         for article in article_list:
             title_name = article.find(name='a', class_='post-item-title').text
             print(title_name)
 
         '''方式2.使用css解析'''
         article_list = response.css('article.post-item')
         for article in article_list:
             title_name = article.css('section>div>a::text').extract_first()  # extract_first()一条 extract()全部
             author_img = article.css('p.post-item-summary>a>img::attr(src)').extract_first()
             desc_list = article.css('p.post-item-summary::text').extract()  # 这个时候取一条回报错 需要把日志功能打开
             desc = desc_list[0].replace('\n', '').replace(' ', '')
             if not desc:
                 desc = desc_list[1].replace('\n', '').replace(' ', '')
             author_name = article.css('footer.post-item-foot>a>span::text').extract_first()
             article_date = article.css('footer.post-item-foot>span>span::text').extract_first()
             print('''
                文章标题:%s
                作者头像:%s
                文章简介:%s
                作者名称: %s
                发布日期:%s
            ''' % (title_name, author_img, desc, author_name, article_date))
 
         '''3.xpath选择器'''
         article_list = response.xpath('//article[contains(@class, "post-item")]')
         for article in article_list:
             title_name = article.xpath('./section/div/a/text()').extract_first()
             author_img = article.xpath('./section/div/p/a/img/@src').extract_first()
             desc_list = article.xpath('./section/div/p/text()').extract()
             desc = desc_list[0].replace('\n', '').replace(' ', '')
             if not desc:
                 desc = desc_list[1].replace('\n', '').replace(' ', '')
             author_name = article.xpath('./section/footer/a/span/text()').extract_first()
             article_date = article.xpath('./section/footer/span/span/text()').extract_first()
             print('''
                文章标题:%s
                作者头像:%s
                文章简介:%s
                作者名称: %s
                发布日期:%s
            ''' % (title_name, author_img, desc, author_name, article_date))

三、Settings相关配置提高爬取效率

基础配置

1.是否遵循爬虫协议
 ROBOTSTXT_OBEY = False# 正常来说你都来爬虫了 还遵循 ?
 2.LOG_LEVEL 日志级别
 LOG_LEVEL='ERROR'# 可以查看具体信息 不会显示无效信息
 3.USER_AGENT# 电脑UA版本信息
 user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
 4.默认请求头
 DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en',
 }
 5.爬虫中间件
 SPIDER_MIDDLEWARES = {
   'cnblogs.middlewares.CnblogsSpiderMiddleware': 543,
 }
 6.下载中间件
 DOWNLOADER_MIDDLEWARES = {
     'cnblogs.middlewares.CnblogsDownloaderMiddleware': 543,
 }
 7.持久化配置
 ITEM_PIPELINES = {
     'cnblogs.pipelines.CnblogsPipeline': 300,
 }
 8.爬虫项目名称
 BOT_NAME = 'myfirstscrapy'
 9.指定爬虫类的Py文件的位置
 SPIDER_MODULES = ['myfirstscrapy.spiders']
 NEWSPIDER_MODULE = 'myfirstscrapy.spiders'

增加爬虫的爬取效率

1.增加并发量
 # 默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改
 CONCURRENT_REQUESTS = 100
 2.降低日志级别
 # 在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。
 LOG_LEVEL = 'INFO'
 3.禁止Cookie
 # 如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。
 COOKIES_ENABLED = False
 4.禁止重试
 # 对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。
 RETRY_ENABLED = False
 5.减少下载超时
 # 如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。
 DOWNLOAD_TIMEOUT = 10 # 超时时间为10s

四、持久化方案 方案

1:保存到本地文件

# 把得到的数据直接保存到本地文件
 import scrapy
 
 class CnblogsSpider(scrapy.Spider):
     name = 'cnblogs'
     allowed_domains = ['www.cnblogs.com']
     start_urls = ['http://www.cnblogs.com/']
 
     def parse(self, response):
        data_list = []
        article_list = response.xpath('//article[contains(@class, "post-item")]')
        for article in article_list:
            title_name = article.xpath('./section/div/a/text()').extract_first()
            author_img = article.xpath('./section/div/p/a/img/@src').extract_first()
            desc_list = article.xpath('./section/div/p/text()').extract()
            desc = desc_list[0].replace('\n', '').replace(' ', '')
            if not desc:
                desc = desc_list[1].replace('\n', '').replace(' ', '')
            author_name = article.xpath('./section/footer/a/span/text()').extract_first()
            article_date = article.xpath('./section/footer/span/span/text()').extract_first()
            print('''
                文章标题:%s
                作者头像:%s
                文章简介:%s
                作者名称: %s
                发布日期:%s
            ''' % (title_name, author_img, desc, author_name, article_date))
 
            data_list.append({
                'title_name': title_name, 'author_img': author_img, 'desc': desc, 'author_name': author_name,
                'article_date': article_date
            })
        return data_list
 
 '''
 这个时候在控制台输入存放的位置即可(json pickle csv)三种格式可选
 scrapy crawl cnblogs -o '文件名称.后缀'
 '''

方案2:保存到数据库

整体流程

1. 使用pipline 常用的,管道形式,可以同时存到多个位置的

2. 在items.py中写一个类(相当于写Django的表模型)继承scrapy.Item

3. 在类中写属性写字段 所有字段都是scrapy.Field类型(title = scrapy.Field())

4. 在爬虫中导入类 实例化得到对象 把要保存的数据放到对象中( item[‘title’] = title )

5. 修改配置文件 指定pipline 数字表示优先级,越小越大 (ITEM_PIPELINES = { ‘crawl_cnblogs.pipelines.CrawlCnblogsPipeline’: 300 })

6. 写一个piplinel类 open_spider: 数据初始化,打开文件,打开数据库链接 process_item:真正存储的地方一定不要忘了return item,交给后续的pipline继续使用 close_spider:销毁资源,关闭文件,关闭数据库链接

cnblogs.py

import scrapy
 from myfirstscrapy.items import CnblogsItem
 
 class CnblogsSpider(scrapy.Spider):
     name = 'cnblogs'
     allowed_domains = ['www.cnblogs.com']
     start_urls = ['http://www.cnblogs.com/']
 
     def parse(self, response):
        # item = CnblogsItem() # 外面定义 会有问题
        article_list = response.xpath('//article[contains(@class, "post-item")]')
        for article in article_list:
            item = CnblogsItem()        # 放在里面每次循环都是新的
            title_name = article.xpath('./section/div/a/text()').extract_first()
            author_img = article.xpath('./section/div/p/a/img/@src').extract_first()
            desc_list = article.xpath('./section/div/p/text()').extract()
            desc = desc_list[0].replace('\n', '').replace(' ', '')
            if not desc:
                desc = desc_list[1].replace('\n', '').replace(' ', '')
            author_name = article.xpath('./section/footer/a/span/text()').extract_first()
            article_date = article.xpath('./section/footer/span/span/text()').extract_first()
 
            item['title_name'] = title_name
            item['author_img'] = author_img
            item['desc'] = desc
            item['author_name'] = author_name
            item['article_date'] = article_date
            yield item

items.py

import scrapy
 
 class CnblogsItem(scrapy.Item):
     title_name = scrapy.Field()
     author_img = scrapy.Field()
     desc = scrapy.Field()
     author_name = scrapy.Field()
     article_date = scrapy.Field()
     article_content = scrapy.Field()

settings.py

ITEM_PIPELINES = {
     'myfirstscrapy.pipelines.CnblogsFilesPipeline': 300,
     'myfirstscrapy.pipelines.CnblogsMysqlPipeline': 200,  # 数字越小 等级越高
 }

pipelines.py

import pymysql
 
 class CnblogsMysqlPipeline:# 名称需要跟settings中配置一样
     def open_spider(self, spider):
         self.conn = pymysql.connect(
             user='root',
             password="123",
             host='127.0.0.1',
             database='cnblogs',
             port=3306,
             autocommit=True,  # 自动提交数据
        )
         self.cursor = self.conn.cursor()
 
     def process_item(self, item, spider):
         self.cursor.execute(  # 不要直接使用字符串拼接 会有风险
             'insert into article (title_name, author_img, `desc`, author_name, article_date) values (%s,%s,%s,%s, %s)',
             args=[item['title_name'], item['author_img'], item['desc'], item['author_name'], item['article_date']])
         # self.conn.commit()     # 提交数据
         return item
 
     def close_spider(self, spider):
         self.cursor.close()
         self.conn.close()
 
 
 class CnblogsFilesPipeline:
     def open_spider(self, spider):
         self.f = open('cnblogs.txt', 'at', encoding='utf-8')
         print('开始了')
 
     def process_item(self, item, spider):
         self.f.write('文章标题:%s, 文章作者: %s \n' % (item['title_name'], item['author_name']))
         return item
 
     def close_spider(self, spider):
         self.f.close()
         print('结束啦')

五、爬虫中间件

Scrapy的所有中间件都写在Middlewares.py中 跟Djagno非常像做一些拦截

爬虫中间件

MyfirstscrapySpiderMiddleware
 def process_spider_input(self, response, spider): # 进入爬虫会执行它
         def process_spider_output(self, response, result, spider): #从爬虫出来会执行它
         def process_spider_exception(self, response, exception, spider):#出了异常会执行
         def process_start_requests(self, start_requests, spider):#第一次爬取执行
         def spider_opened(self, spider): #爬虫开启执行
         
 返回值:
 return a Response object:正常,会进入到引擎,引擎把它给爬虫
 return a Request object: 会进入到引擎,引擎把它放到调度器中,等待下次爬取
     raise IgnoreRequest     会执行process_exception

下载中间件

MyfirstscrapyDownloaderMiddleware
 def process_request(self, request, spider): # request对象从引擎进入到下载器会执行
    def process_response(self, request, response, spider):# response对象从下载器进入到引擎会执行
    def process_exception(self, request, exception, spider):#出异常执行它
    def spider_opened(self, spider): #爬虫开启执行它
 
 返回值
 return None: 继续执行下面的中间件的process_request
 return a Response object: 不进入下载中间件了,直接返回给引擎,引擎把它通过6给爬虫
 return a Request object:不进入中间件了,直接返回给引擎,引擎把它放到调度器中
 raise IgnoreRequest: process_exception() 抛异常,会执行process_exception

转载自:CSDN-MeiJin_

YXN-python

2022-03-10