• Scrapy项目实战


    Date: 2019-07-15

    Author: Sun

    Scrapy是一个为了爬取网站数据、提取结构化数据而编写的爬虫应用框架。Scrapy内部实现了包括并发请求、免登录、URL去重等很多复杂操作,用户不需要明白Scrapy内部具体的爬取策略,只需要根据自己的需要,编写小部分的代码,就能抓取到所需要的数据

    此节我们学习下如何采用采用scrapy进行项目流程开发和配置

    一 项目准备工作

    1. 创建爬虫项目

    使用startproject命令创建项目

    scrapy startproject  scrapy_proj   #使用scrapy产生一个scrapy_name爬虫项目
    

    2 生成爬虫项目

    使用genspider命令在项目中创建爬虫脚本

    cd   scrapy_proj/scrapy_proj
    scrapy genspider  --list
    scrapy genspider myspider  "www.myspider_domain.com" 
    

    此时会在scrapy_proj/spiders/产生一个新的文件myspider.py

    这个是我们的爬取页面的主入口和页面下载完成后解析主入口。

    Scrapy项目文件结构

    • items.py 负责数据模型的建立,类似于实体类。
    • middlewares.py 自己定义的中间件。
    • pipelines.py 负责对spider返回数据的处理。 (管道文件)
    • settings.py 负责对整个爬虫的配置。 (项目配置)
    • spiders目录 负责存放继承自scrapy的爬虫类。(写代码的位置)
    • scrapy.cfg scrapy基础配置

    3 项目配置

    设置settings.py 文件,设置相关的配置信息,具体配置见下面参数的说明

    配置文件参数说明

    (1)ROBOTSTXT_OBEY = True ————— 是否遵守robots.txt规则

    说明:

    ​ robots.txt 是遵循 Robot协议 的一个文件,它保存在网站的服务器中,它的作用是,告诉搜索引擎爬虫,本网站哪些目录下的网页 不希望 你进行爬取收录。在Scrapy启动后,会在第一时间访问网站的 robots.txt 文件,然后决定该网站的爬取范围。(在某些情况下我们想要获取的内容恰恰是被 robots.txt 所禁止访问的。所以,某些时候,我们就要将此配置项设置为 False ,拒绝遵守 Robot协议 !)

    (2)CONCURRENT_REQUESTS = 16-----------开启线程数量,默认16,可以自行设置

    ​ 这个参数涉及到scrapy爬取的并发量,items的处理速度

    (3)DOWNLOAD_DELAY = 3 ——— 下载延迟时间。下载器在下载同一个网站下一个页面前需要等待的时间。该选项可以用来限制爬取速度, 减轻服务器压力。(反爬策略之一)

    (4)CONCURRENT_REQUESTS_PER_DOMAIN = 16 将对任何单个域执行的并发(即同时)请求的最大数量。

    ​ CONCURRENT_REQUESTS_PER_IP = 16 将对任何单个IP执行的并发(即同时)请求的最大数量。如果非零,CONCURRENT_REQUESTS_PER_DOMAIN则忽略该 设置,而改为使用此设置。换句话说,并发限制将应用于每个IP,而不是每个域。

    (5)COOKIES_ENABLED = False

    ​ 是否启用cookie。是否启用cookies middleware。如果关闭,cookies将不会发送给web server。

    ​ 除非您真的 需要,否则请禁止cookies。在进行通用爬取时cookies并不需要, (搜索引擎则忽略cookies)。禁止cookies能减少CPU使用率及Scrapy爬虫在内存中记录的踪迹,提高性能。

    ​ COOKIES_DEBUG:默认: False

    ​ 如果启用,Scrapy将记录所有在request(cookie 请求头)发送的cookies及response接收到的cookies(set-cookie接收头)

    (6)AUTOTHROTTLE_START_DELAY = 5

    ​ 初始下载延迟时间(单位:秒)

    (7)AUTOTHROTTLE_MAX_DELAY = 60

    ​ 高并发请求时最大延迟时间(单位:秒)

    (8) USER_AGENT 用户代理

    ​ 这个是至关重要的,大部分服务器在请求快了会首先检查User_Agent,而scrapy默认的浏览器头是scrapy1.1 我们需要开启并且修改成浏览器头,如:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1。 但是最好是这个USER-AGENT会随机自动更换最好了。

    (8)DEFAULT_REQUEST_HEADERS

    ​ 默认请求头部信息,例如如下配置

    DEFAULT_REQUEST_HEADERS = {
        'accept': 'image/webp,*/*;q=0.8',
        'accept-language': 'zh-CN,zh;q=0.8',
        'referer': 'https://www.taobao.com/',
        'user-agent': 'Mozilla/5.0 (Windows NT 6.3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36',
    }
    

    这个是浏览器请求头,很多网站都会检查客户端的headers,比如豆瓣就是每一个请求都检查headers的user_agent,否则只会返回403,可以开启user-agent

    (9) SPIDER_MIDDLEWARES

    ​ Spider中间件是介入到Scrapy中的spider处理机制的钩子框架,可以插入自定义功能来处理发送给 Spiders 的response,以及spider产生的item和request。

    要启用Spider中间件(Spider Middlewares),可以将其加入到 SPIDER_MIDDLEWARES 设置中。 该设置是一个字典,键为中间件的路径,值为中间件的顺序(order)。

    (10) DOWNLOADER_MIDDLEWARES

    ​ 要激活下载器中间件组件,将其加入到 DOWNLOADER_MIDDLEWARES 设置中。 该设置是一个字典(dict),键为中间件类的路径,值为其中间件的顺序(order)。

    (11)ITEM_PIPELINES

    每个Item Pipeline组件其实就是一个实现了一个简单方法的Python类。他们接受一个item并在上面执行逻辑,还能决定这个item到底是否还要继续往下传输,如果不要了就直接丢弃。

    (12)AUTOTHROTTLE — 自动限速 (反爬策略之一)

    AUTOTHROTTLE_ENABLED = True  #初始下载延迟
    # The initial download delay
    AUTOTHROTTLE_START_DELAY = 5   #在高延迟的情况下设置的最大下载延迟
    # The maximum download delay to be set in case of high latencies
    AUTOTHROTTLE_MAX_DELAY = 60    #Scrapy请求的平均数量应该并行发送每个远程服务器
    # The average number of requests Scrapy should be sending in parallel to
    # each remote server
    AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0    
    # Enable showing throttling stats for every response received:
    AUTOTHROTTLE_DEBUG = False
    

    (13)是否启用在本地缓存,如果开启会优先读取本地缓存,从而加快爬取速度,视情况而定

    HTTPCACHE_ENABLED = True

    HTTPCACHE_EXPIRATION_SECS = 0

    HTTPCACHE_DIR = 'httpcache'

    HTTPCACHE_IGNORE_HTTP_CODES = []

    HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'

    如何在scrapy中加入日志功能?

    答案:在settings.py中加入如下信息

    LOG_LEVEL= 'INFO'        #日志级别
    
    LOG_FILE ='log.txt'      #日志打印的文件名称
    

    DEBUG < INFO < WARNING < ERROR

    日志案例:(settings.py中设置如下信息)

    ###############   log settings begin   ######################
    
    LOG_LEVEL = "INFO"
    
    from datetime import datetime
    import os
    
    today = datetime.now()
    
    LOG_DIR = "logs"
    if not os.path.exists(LOG_DIR):
       os.mkdir(LOG_DIR)
    
    LOG_FILE = "{}/scrapy_{}_{}_{}.log".format(LOG_DIR, today.year, today.month, today.day)
    
    ###############   log settings end   ######################
    

    二。案例分析

    糗事百科的主页:www.qiushibaike.com

    1 创建爬虫项目

    使用startproject命令创建项目(糗事百科爬虫项目)

    scrapy startproject  qiubai_proj   #使用scrapy产生一个scrapy_name爬虫项目
    

    2 生成创建爬虫脚本

    使用genspider命令在项目中创建爬虫脚本

    cd   qiubai_proj/qiubai_proj
    scrapy genspider qiubai  "www.qiushibaike.com" 
    

    此时会在qiubai_proj/spiders/产生一个新的文件qiubai.py

    这个是我们的爬取页面的主入口和页面下载完成后解析主入口。

    修改配置文件settings.py:

    BOT_NAME = 'qiubai_proj'
    
    SPIDER_MODULES = ['qiubai_proj.spiders']
    NEWSPIDER_MODULE = 'qiubai_proj.spiders'
    
    
    # Crawl responsibly by identifying yourself (and your website) on the user-agent
    #USER_AGENT = 'qiubai_proj (+http://www.yourdomain.com)'
    USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) " \
              "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"
    # Obey robots.txt rules
    #ROBOTSTXT_OBEY = True
    ROBOTSTXT_OBEY = False
    
    LOG_LEVEL= 'DEBUG'
    

    3. 数据模型

    分析糗事百科网站,定义数据模型用于保存爬取的数据。

    编辑文件qiubai_proj/qiubai_proj/items.py, 内容如下:

    # -*- 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 QiubaiProjItem(scrapy.Item):
        # define the fields for your item here like:
        #保存头像链接
        image_url = scrapy.Field()
        #保存名字
        name = scrapy.Field()
        #保存年龄
        age = scrapy.Field()
        #保存内容
        content = scrapy.Field()
        #好笑的个数
        haha_count = scrapy.Field()
    

    4. spiders中编写爬取逻辑

    关于xpath语法使用请参考day03中的xpath语法章节。

    # -*- coding: utf-8 -*-
    import scrapy
    
    
    class QiubaiSpider(scrapy.Spider):
        name = 'qiubai'
        allowed_domains = ['www.qiushibaike.com']
        start_urls = ['http://www.qiushibaike.com/']  #主入口url
    
        #parse函数就是文件解析函数,response就是响应对象
        def parse(self, response):
            # with open("qiubai.html", 'w', encoding="utf-8") as f:
            #     f.write(response.text)
            div_list = response.xpath('//div[starts-with(@id, "qiushi_tag_")]')
            print(type(div_list))
            #遍历列表,获取列表内容
            item_list = []
            for div in div_list: 
                '''
                先通过xpath获取内容,返回的是一个列表
                然后通过extract()转换成unicode字符串,再获取第0个, 也就是指定的内容
                将解析到的内容保存到字典中
                '''
                image_url = div.xpath('./div[@class="author clearfix"]//img/@src').extract_first()
                name = div.xpath('./div[@class="author clearfix"]//h2/text()').extract_first()
                age = div.xpath('./div[@class="author clearfix"]/div/text()').extract_first()
                content = div.xpath('./a/div[@class="content"]/span/text()').extract()
                content = ' '.join(content)
                haha_count = div.xpath('./div[@class="stats"]/span[@class="stats-vote"]/i/text()').extract()[0]
                item = dict(
                    image_url = image_url,
                    name = name,
                    age = age,
                    content = content,
                    haha_count = haha_count
                )
                yield item
                #item_list.append(item)
                
            #return item_list
    

    5 运行spider

    5.1 数据格式化输出:

    (1)保存爬取的内容到json文件中

    ​ scrapy crawl qiubai -o qiubai.json

    ​ 可以查看产生的json文件,将内容拷贝到json在线格式网站

    https://www.json.cn/, 看数据爬取是否和真实相符。

    ​ (2)保存爬取的数据到xml文件中

    ​ scrapy crawl qiubai -o qiubai.xml

    ​ (3)保存爬取的数据到数据报表csv文件中

    ​ scrapy crawl qiubai -o qiubai.csv

    知识总结:

    通过指令创建爬虫文件
    
       cd qiubai_proj/qiubai_proj
    
       scrapy genspider qiubai "www.qiushibaike.com"
    
       那么就会在firstSpider/firstSpider/spiders里面自动创建一个qiubai.py
    
          name: 爬虫的名字,启动的时候根据爬虫的名字启动项目
    
          allowed_domains:允许的域名,就是爬取的时候这个请求要不要发送,如果是该允许域名之下的url,就会发送,如果不是,则过滤掉这个请求,这是一个列表,可以写多个允许的域名
    
          start_urls:爬虫起始url,是一个列表,里面可以写多个,一般只写一个
    
          def parse(self, response): 这个函数非常重要,就是你以后写代码的地方,parse函数名是固定的,当收到下载数据的时候会自动的调用这个方法,该方法第二个参数为response,这是一个响应对象,从该对象中获取html字符串,然后解析之。
    
    

    6. 数据持久化

    糗事百科的主页:www.qiushibaike.com

    本节我们将对上节数据进行数据保存(持久化)

    本节分别将数据保存到json文件和mysql数据库中

    6.1 保存数据到json文件

    ​ 使用 Scrapy 提供的 exporter 存储 Json 数据

    ​ Scrapy 为我们提供了一个 JsonItemExporter 类来进行 Json 数据的存储,非常方便

    1. 修改上节中的spiders

      使用yield改造,使得spider成为一个生成器,不断地往pipeline里面流入待处理的数据

    # -*- coding: utf-8 -*-
    import scrapy
    
    
    class QiubaiSpider(scrapy.Spider):
    	name = 'qiubai'
    	allowed_domains = ['www.qiushibaike.com']
    	start_urls = ['http://www.qiushibaike.com/']  # 主入口url
    
    	# parse函数就是文件解析函数,response就是响应对象
    	def parse(self, response):
    		# with open("qiubai.html", 'w', encoding="utf-8") as f:
    		#     f.write(response.text)
    		div_list = response.xpath('//div[starts-with(@id, "qiushi_tag_")]')
    		#print(type(div_list))
    		# 遍历列表,获取列表内容
    		item_list = []
    		for div in div_list:
    			'''
    			先通过xpath获取内容,返回的是一个列表
    			然后通过extract()转换成unicode字符串,再获取第0个, 也就是指定的内容
    			将解析到的内容保存到字典中
    			'''
    			image_url = div.xpath('./div[@class="author clearfix"]//img/@src').extract_first()
    			name = div.xpath('./div[@class="author clearfix"]//h2/text()').extract_first().strip("\n")
    			age = div.xpath('./div[@class="author clearfix"]/div/text()').extract_first()
    			contents = div.xpath('./a/div[@class="content"]/span/text()').extract()
    			content = ' '.join([ct.strip() for ct in contents])
    			haha_count = div.xpath('./div[@class="stats"]/span[@class="stats-vote"]/i/text()').extract()[0]
    			item = dict(
    				image_url=image_url,
    				name=name,
    				age=age,
    				content=content,
    				haha_count=haha_count
    			)
    			yield item
    			#     item_list.append(item)
    			# return item_list
    
    1. 首先要开启pipeline开关

    在settings.py文件中开启ITEM_PIPELINES选项,开启如下信息

    # Configure item pipelines
    # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
    ITEM_PIPELINES = {
       'qiubai_proj.pipelines.QiubaiProjPipeline': 300,
    }
    
    1. pipelines的逻辑处理

      通过pipelines将spiders转发过来的item数据进行导入到json报表中

      编辑qiubai_proj/pipelines.py文件,添加如下内容

      # -*- 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
      from scrapy.exporters import JsonItemExporter
      
      class QiubaiProjPipeline(object):
          # 调用 scrapy 提供的 json exporter 导出 json 文件
          def __init__(self):
              self.file = open('questions_exporter.json', 'wb')
              # 初始化 exporter 实例,执行输出的文件和编码
              self.exporter = JsonItemExporter(self.file, encoding='utf-8', ensure_ascii=False)
              # 开启倒入数据
              self.exporter.start_exporting()
              
              
         def close_spider(self, spider):
             self.exporter.finish_exporting()
             self.file.close()
             
             
         def process_item(self, item, spider):
             '''
             使用 scrapy.exporters.JsonItemExporter 生成的文件
             '''
             print("########### pipelines begin ###############")
             #print(item)
             self.exporter.export_item(item)
             print("########### pipelines end ###############")
             return item
      

    上述pipeline将统计的item结果数据保存到questions_exporter.json中

    1. 添加日志功能

      settings.py中添加日志功能 文件及路径,log目录需要先建好

      today = datetime.now()
      log_file_path = "log/scrapy_{}{}{}.log".format(today.year, today.month, today.day)

    日志输出

    LOG_LEVEL = 'DEBUG'
    LOG_FILE = log_file_path

    1. 运行spider

    ​ scrapy crawl qiubai

    ​ 运行成功后,可以查看questions_exporter.json文件

    6.2 保存数据到mysql数据库

    依赖库:pymysql

    Twisted 是一个异步网络框架,不幸的是大部分数据库api实现只有阻塞式接口,twisted.enterprise.adbapi为此产生,它是DB-API 2.0 API的非阻塞接口,可以访问各种关系数据库。

    不同于直接创建数据库连接, 而是使用 adbapi.ConnectionPool 类来管理连接. 这就可以让 adbapi 来使用多个连接, 比如每个线程一个连接,这很简单:

    使用前面例子的 "dbmodule" , 来创建一个 ConnectionPool 对象

    from twisted.enterprise import adbapi连接mysql,要这样:

    _conn = adbapi.ConnectionPool(' db='test', user='root', passwd='aaaa', host='localhost',use_unicode=True, charset='utf8'*)

    1. 数据库定义

      创建数据库

      create database  qiubai_db   default charset=utf8;  
      
      CREATE TABLE qiubai ( 
      
        id int(11) PRIMARY  KEY  auto_increment COMMENT '设置主键自增',
      
        image_url varchar(150) NOT NULL COMMENT '图片url链接',
      
        name VARCHAR(50) COMMENT '名称',
      
        age VARCHAR(10) COMMENT '年龄',
      
        content VARCHAR(500) COMMENT '内容',
      
        haha_count INT COMMENT '笑点数'
      
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      

    将上述内容放入到table.sql文件,进入mysql终端中,执行 source ./table.sql;

    ​ 创建数据库和表成功!

    1. 修改配置文件

    settings.py文件中添加如下mysql数据库配置信息:

    MYSQL_SETTINGS = {
    
       'HOST':'192.168.51.63',    #数据库地址
       'DATABASE':'qiubai_proj',    #数据库名称
       'USER':'zhougy',           #登陆用户名
       'PASSWORD':'123456',       #登陆密码
       'CHARSET':'utf8',          #字符编码
    
    }
    
    1. 添加数据库操作的pipeline

    添加文件 mysql_pipelines.py, 内容添加如下:

    scrapy框架底层网络库是twisted,同时他也

    此文件采用twisted.enterprise.adbapi异步处理接口处理mysql操作

    # -*- coding: utf-8 -*-  
    __author__ = 'zhougy'
    __date__ = '2018/9/2 下午7:26'
    import json
    import pymysql
    from twisted.enterprise import adbapi
    from .settings import MYSQL_SETTINGS
    import logging
    
    class MysqlTwistedPipline(object):
    	def __init__(self, dbpool):
    		self.dbpool = dbpool
    
    	@classmethod
    	def from_settings(cls, settings):
    		dbparms = dict(
    			host=MYSQL_SETTINGS['HOST'],
    			db=MYSQL_SETTINGS['DATABASE'],
    			user=MYSQL_SETTINGS['USER'],
    			passwd=MYSQL_SETTINGS['PASSWORD'],
    			charset=MYSQL_SETTINGS['CHARSET'],
    			cursorclass=pymysql.cursors.DictCursor,  # 指定 curosr 类型
    			use_unicode=True,
    		)
    		# 指定擦做数据库的模块名和数据库参数参数
    		dbpool = adbapi.ConnectionPool("pymysql", **dbparms)
    		return cls(dbpool)
    
    	def process_item(self, item, spider):
    		'''
    		使用twisted将mysql插入变成异步处理
    		:param item:
    		:param spider:
    		:return:
    		'''
    		query = self.dbpool.runInteraction(self.db_insert, item)
    		# 指定异常处理方法
    		query.addErrback(self.handle_error, item, spider)  # 处理异常
    		return item
    		
    	def handle_error(self, failure, item, spider):
    		# 处理异步插入的异常
    		print("######################1")
    		#print(item)
    		#print(failure)
    		logging.error(f"handler_error has error failure: {failure}")
    		logging.warning()
    		print("########################2")	
    		
    	def db_insert(self, cursor, item):
    		# 执行具体的插入
    		# 根据不同的item 构建不同的sql语句并插入到mysql中
    		insert_sql, params = self.get_insert_sql(item)
    		cursor.execute(insert_sql, params)
    		logging.info(f"write db ok with data:{item['name']}")
    		
    	def get_insert_sql(self, item):
    		insert_sql = """
    		                 insert into qiubai(image_url, name, age, content, haha_count)
    		                 VALUES (%s, %s, %s, %s, %s)
    		             """
    		params = (
    			item["image_url"], item["name"], item["age"], item["content"],
    			item["haha_count"])
    		return insert_sql, params
    
    

    ​ 至此,基于mysql的pipeline异步处理管道逻辑就已经完成。

    修改配置文件settings.py

    将上述MysqlTwistedPipline管道处理类添加到配置文件中

    ITEM_PIPELINES = {
    
    'qiubai_proj.pipelines.QiubaiProjPipeline': 300,
    
       'qiubai_proj.mysql_pipelines.MysqlTwistedPipline': 300,
    }
    

    运行启动spider

    scrapy crawl qiubai

    运行成功后,可以查看数据库表数据。

  • 相关阅读:
    C++17 filesystem文件系统
    简易版本vue的实现
    javaSE基础04
    javaSE基础03
    javaSE基础02
    JavaSE基础01
    Document对象和window对象
    进程和子进程及端口的常用命令
    vue分页组件二次封装---每页请求特定数据
    css图片垂直水平居中及放大(实现水平垂直居中的效果有哪些方法?)
  • 原文地址:https://www.cnblogs.com/sunBinary/p/11186596.html
Copyright © 2020-2023  润新知