Scrapy框架在Python中如何高效抓取网页数据?

2026-05-19 12:241阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计5302个文字,预计阅读时间需要22分钟。

Scrapy框架在Python中如何高效抓取网页数据?

目录 + Scrapy 框架 + 简介 + 1. 介绍 + 2. 介绍 + 3. 环境配置 + 4. 常用命令 + 运行原理 + 4.1 流程图 + 4.2 流程图 + 4.3 组件简介 + 4.4 运行流程 + 二、创建项目 + 1. 修改配置 + 2. 创建项目 + 3. 定义数据 + 4. 编写代码

目录
  • Scrapy 框架
    • 一、 简介
      • 1、 介绍
      • 2、 环境配置
      • 3、 常用命令
      • 4、 运行原理
        • 4.1 流程图
        • 4.2 部件简介
        • 4.3 运行流程
    • 二、 创建项目
      • 1、 修改配置
      • 2、 创建一个项目
      • 3、 定义数据
      • 4、 编写并提取数据
      • 5、 存储数据
      • 6、 运行文件
    • 三、 日志打印
      • 1、 日志信息
      • 2、 logging 模块
    • 四、 全站爬取
      • 1、 使用request排序入队
      • 2、 继承crawlspider
    • 五、 二进制文件
      • 1、 图片下载
    • 六、 middlewares
      • 1、下载中间件
      • 2、 爬虫中间件
    • 七、 模拟登录
      • 1、 cookie
      • 2、 直接登录
    • 八、 分布式爬虫
      • 1、概念
      • 2、 用法

Scrapy 框架 一、 简介 1、 介绍

Scrapy 是一个基于 Twisted 实现的异步处理爬虫框架,该框架使用纯 Python 语言编写。Scrapy 框架应用广泛,常用于数据采集、网络监测,以及自动化测试等

2、 环境配置
  1. 安装 pywin32
    • pip install pywin32
  2. 安装 wheel
    • pip install wheel
  3. 安装 twisted
    • pip install twisted
  4. 安装 scrapy 框架
    • pip install scrapy
3、 常用命令 命令 格式 说明 startproject scrapy startproject <项目名> 创建一个新项目 genspider scrapy genspider <爬虫文件名> <域名> 新建爬虫文件 runspider scrapy runspider <爬虫文件> 运行一个爬虫文件,不需要创建项目 crawl scrapy crawl <spidername> 运行一个爬虫项目,必须要创建项目 list scrapy list 列出项目中所有爬虫文件 view scrapy view <url地址> 从浏览器中打开 url 地址 shell scrapy shell <url地址> 命令行交互模式 settings scrapy settings 查看当前项目的配置信息 4、 运行原理 4.1 流程图 4.2 部件简介
  1. 引擎(Engine)

    引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。

  2. 调度器(Scheduler)

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

  3. 下载器(Downloader)

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

  4. 爬虫(Spiders)

    是开发人员自定义的类,它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器)

  5. 项目管道(Item Pipeline)

    在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作

  6. 下载中间件(Downloader Middlerwares)

    你可以当作是一个可以自定义扩展下载功能的组件。

  7. 爬虫中间件(Spider Middlerwares)

    Scrapy框架在Python中如何高效抓取网页数据?

    位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)

4.3 运行流程
  1. 引擎:Hi!Spider, 你要处理哪一个网站?
  2. Spider:老大要我处理xxxx.com。
  3. 引擎:你把第一个需要处理的URL给我吧。
  4. Spider:给你,第一个URL是xxxxxxx.com。
  5. 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
  6. 调度器:好的,正在处理你等一下。
  7. 引擎:Hi!调度器,把你处理好的request请求给我。
  8. 调度器:给你,这是我处理好的request
  9. 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求
  10. 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)
  11. 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)
  12. Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
  13. 引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
  14. 管道调度器:好的,现在就做!

注意:只有当调度器没有request需要处理时,整个程序才会停止。(对于下载失败的URL,Scrapy也会重新下载。)

二、 创建项目

本次示例是爬取豆瓣

1、 修改配置

LOG_LEVEL = "WARNING" # 设置日志等级 from fake_useragent import UserAgent USER_AGENT = UserAgent().random # 设置请求头 ROBOTSTXT_OBEY = False # 是否遵守 robots 协议,默认为 True ITEM_PIPELINES = { # 开启管道 'myFirstSpider.pipelines.MyfirstspiderPipeline': 300, # 300 为权重, 'myFirstSpider.pipelines.DoubanPipeline': 301, # 数字越大权重越小 } 2、 创建一个项目

在命令行输入:

(scrapy_) D:\programme\Python\scrapy_>scrapy startproject myFirstSpider (scrapy_) D:\programme\Python\scrapy_>cd myFirstSpider (scrapy_) D:\programme\Python\scrapy_\myFirstSpider>scrapy genspider douban "douban.com" 3、 定义数据

定义一个提取的结构化数据(Item)

  1. 打开 myFirstSpider 目录下的 items.py
  2. item 定义结构化的数据字段,用来存储爬取到的数据,有点像python里面的字典,但是提供了一些而外的保护减少错误
  3. 可以通过创建一个 scrapy.Item 类,并且定义类型为 scrapy.Field 的类属性来定义一个 Item (可以理解成类似于ORM的映射关系)
  4. 接下来,创建一个 Douban 类,和构建 item 模型

# Define here the models for your scraped items # # See documentation in: # docs.scrapy.org/en/latest/topics/items.html import scrapy class MyfirstspiderItem(scrapy.Item): # 可以自己创建一个类,但是要继承 scrapy.Item 类 # define the fields for your item here like: # name = scrapy.Field() pass class DoubanItem(scrapy.Item): title = scrapy.Field() # 标题 introduce = scrapy.Field() # 介绍 4、 编写并提取数据

编写爬取网站的 Spider 并提取出结构化数据(Item)

在定义的爬虫文件中写入:

import scrapy from ..items import DoubanItem # 导入定义的格式化数据 class DoubanSpider(scrapy.Spider): name = 'douban' # 爬虫的识别名称,唯一的 # allowed_domains = ['douban.com'] # 允许爬取的范围 # start_urls = ['douban.com/'] # 最初爬取的 url start_urls = ['movie.douban.com/top250'] # 可以自己定义要爬取的 url def parse(self, response): info = response.xpath('//div[@class="info"]') for i in info: # 存放电影信息合集 item = DoubanItem() title = i.xpath("./div[1]/a/span[1]/text()").extract_first() # 获取第一个内容,通过extract方法提取selector对象 introduce = i.xpath("./div[2]/p[1]//text()").extract() # 获取全部内容 introduce = "".join(j.strip() for j in [i.replace("\\xa0", '') for i in introduce]) # 整理信息 item["title"] = title item["introduce"] = introduce # 将获取的数据交给 pipeline yield item 5、 存储数据

编写 Item Pipelines 来存储提取到的 Item (即结构化数据)

# Define your item pipelines here # # Don't forget to add your pipeline to the ITEM_PIPELINES setting # See: docs.scrapy.org/en/latest/topics/item-pipeline.html # useful for handling different item types with a single interface from itemadapter import ItemAdapter class MyfirstspiderPipeline: def process_item(self, item, spider): return item class DoubanPipeline: # 在爬虫文件开始时,运行此函数 def open_spider(self, spider): if spider.name == "douban": # 如果数据是从豆瓣爬虫传进来的 print("爬虫开始运行!") self.fp = open("./douban.txt", "w", encoding="utf-8") def process_item(self, item, spider): if spider.name == "douban": self.fp.write(f"标题:{item['title']}, 信息:{item['introduce']}") # 保存文件 # 爬虫结束的时候运行 def close_spider(self, spider): if spider.name == "douban": print("爬虫结束运行!") self.fp.close() 6、 运行文件

(scrapy_) D:\programme\Python\scrapy_\myFirstSpider>scrapy crawl douban 三、 日志打印 1、 日志信息

日志信息等级:

  • ERROR:错误信息
  • WARNING:警告
  • INFO:一般的信息
  • DEBUG:调试信息

设置日志信息的制定输出

LOG_LEVEL = "ERROR" # 指定日志信息种类 LOG_FILE = "log.txt" # 表示将日志信息写到指定的文件中进行存储 2、 logging 模块

imoprt logging logger = logging.getLogger(__name__) # __name__ 获得项目的文件名 logger.warning(" info ") # 打印要输出的日志信息 四、 全站爬取 1、 使用request排序入队

yield scrapy.Request(url=new_url, callback=self.parse_taoche, meta={"page": page})

参数:

  • url:传递的地址
  • callback:请求后响应数据的处理函数
  • meta:传递数据
    • 每次请求都会携带meta参数
    • 传递给响应
    • 可以通过response.meta \ response.meta["page"]获取

import scrapy, logging from ..items import DetailItem logger = logging.Logger(__name__) class DoubanSpider(scrapy.Spider): name = 'douban' # allowed_domains = ['douban.com'] start_urls = ['movie.douban.com/top250'] def parse(self, response): print(response) info = response.xpath('//div[@class="info"]') for i in info: item_detail = DetailItem() # 详情页的内容 # 存放电影信息合集 title = i.xpath("./div[1]/a/span[1]/text()").extract_first() # 获取第一个内容,通过extract方法提取selector对象 item_detail["title"] = title logger.warning(title) detail_url = i.xpath("./div[1]/a/@href").extract_first() # 获取详情页的url # print(detail_url) yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta=item_detail) # 将请求传递给调度器,重新请求 next_url = response.xpath("//div[@class='paginator']/span[3]/a/@href").extract_first() # 获取下一页的url if next_url: next_url = "movie.douban.com/top250" + next_url # print(next_url) yield scrapy.Request(url=next_url, callback=self.parse, ) # 将请求传递给调度器,重新请求 def parse_detail(self, resp): item = resp.meta # 接收结构化数据 introduce = resp.xpath("//div[@id='link-report']/span[1]/span//text()").extract() # 获取介绍 item["introduce"] = introduce logger.warning(introduce) content = resp.xpath("//div[@id='hot-comments']/div[1]//text()").extract() # 获取评论 item["content"] = content logger.warning(content) yield item 2、 继承crawlspider

Scrapy框架中分为两类爬虫:

  • Spider
  • CrawlSpider:
    • CrawlSpider是Spider的派生类,Spider类的设计原理是指爬取start_url列表中的网页,而CrwalSpider类定义了一些规则来提供跟进链接的方便的机制,从爬取的网页中获取链接并继续爬取的工作更合理

创建方法:

scrapy genspider -t crawl 项目名称 网站

创建后,其显示为

import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class FuhaoSpider(CrawlSpider): name = 'fuhao' # allowed_domains = ['fuhao.com'] start_urls = ['www.phb123.com/renwu/fuhao/shishi_1.html'] rules = ( Rule( LinkExtractor(allow=r'shishi_\d+.html'), # 链接提取器,根据正则规则提取url callback='parse_item', # 指定回调函数 follow=True # 获取的响应页面是否再次经过rules来进行提取url地址 ), ) def parse_item(self, response): print(response.request.url)

Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True)

  • LinkExtractor:链接提取器,根据正则规则提取url地址
  • callback:提取出来的url地址发送请求获取响应,会把响应对象给callback指定的函数进行处理
  • follow:获取的响应页面是否再次经过rules来进行提取url地址

# 匹配豆瓣 start_urls = ['movie.douban.com/top250?start=0&filter='] rules = ( Rule(LinkExtractor(allow=r'?start=\d+&filter='), callback='parse_item', follow=True), )

五、 二进制文件 1、 图片下载

ImagesPipeLine:图片下载的模块

pipeline中,编写代码(已知,item里面传输的是图片的下载地址)

import logging import scrapy from itemadapter import ItemAdapter from scrapy.pipelines.images import ImagesPipeline # 继承ImagesPipeLine class PicPipeLine(ImagesPipeline): # 根据图片地址,发起请求 def get_media_requests(self, item, info): src = item["src"] # item["src] 里面存储的是图片的地址 logging.warning("正在访问图片:", src) yield scrapy.Request(url = src,meta={'item':item}) # 对图片发起请求 # 指定图片的名字 def file_path(self, request, response=None, info=None, *, item=None): item = request.meta['item'] # 接收meta参数 return request.url.split("/")[-1] # 设置文件名字 # 在settings中设置 IMAGES_STORE = "./imags" # 设置图片保存的文件夹 # 返回数据给下一个即将被执行的管道类 def item_completed(self, results, item, info): return item 六、 middlewares 1、下载中间件

更换代理IP,更换Cookies,更换User-Agent,自动重试

在settings.py 中添加

# 建立ip池 PROXY_LIST = []

在middlewares.py中添加

from fake_useragent import UserAgent import random class Spider4DownloaderMiddleware: # 拦截所有请求 def process_request(self, request, spider): # UA 伪装 request.headers["User-Agent"] = UserAgent().random return None # 处理请求,可以篡改响应信息,spider是实例化的爬虫对象 def process_response(self, request, response, spider): bro = spider.bro # 在爬虫对象中创建了的selenium对象 if request.url in spider.model_urls: # print(request.url) # 要篡改request请求的响应对象, response bro.get(request.url) # 执行js代码 bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') # 一拉到底,发现我们滚动条还是在中间位置 bottom = [] # 空列表,表示没有到底部 while not bottom: # bool([]) ==> false not false bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') page_text = bro.page_source # 获取页面内容 # 如果到底,循环结束 bottom = re.findall(r'<div class="load_more_tip" style="display: block;">:-\)已经到最后啦~</div>', page_text) time.sleep(1) if not bottom: try: bro.find_element(By.CSS_SELECTOR, '.load_more_btn').click() # 找到加载更多进行点击 except: bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') return HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request) # body 是要返回的数据 return response # 处理异常,当网络请求失败时,执行此函数 def process_exception(self, request, exception, spider): # 添加代理ip type_ = request.url.split(":")[0] request.meta['proxy'] = f"{type_}://{random.choice(spider.settings.get('PROXY_LIST'))}" return request # 如果ip被封了,就使用代理ip,重新发送请求 # 开始爬虫时执行 def spider_opened(self, spider): spider.logger.info('Spider opened: %s' % spider.name)

设置完下载中间件后,要在settings配置文件中开启

2、 爬虫中间件

爬虫中间件的用法与下载器中间件非常相似,只是它们的作用对象不同。下载器中间件的作用对象是请求request和返回response;爬虫中间件的作用对象是爬虫,更具体地来说,就是写在spiders文件夹下面的各个文件

  1. 当运行到yield scrapy.Request()或者yield item的时候,爬虫中间件的process_spider_output()方法被调用
  2. 当爬虫本身的代码出现了Exception的时候,爬虫中间件的process_spider_exception()方法被调用
  3. 当爬虫里面的某一个回调函数parse_xxx()被调用之前,爬虫中间件的process_spider_input()方法被调用
  4. 当运行到start_requests()的时候,爬虫中间件的process_start_requests()方法被调用

import scrapy class Spider5SpiderMiddleware: # 在下载器中间件处理完成后,马上要进入某个回调函数parse_xxx()前调用 def process_spider_input(self, response, spider): return None # 在爬虫运行yield item或者yield scrapy.Request()的时候调用 def process_spider_output(self, response, result, spider): for item in result: print(result) if isinstance(item, scrapy.Item): # 这里可以对即将被提交给pipeline的item进行各种操作 print(f'item将会被提交给pipeline') yield item # 也可以 yield request,当为yield request时,可以修改请求信息,如meta等 # 当在爬虫程序运行过程中报错时调用 def process_spider_exception(self, response, exception, spider): """ 爬虫里面如果发现了参数错误,就使用raise这个关键字人工抛出一个自定义的异常。在实际爬虫开发中,可以在某些地方故意不使用try ... except捕获异常,而是让异常直接抛出。例如XPath匹配处理的结果,直接读里面的值,不用先判断列表是否为空。这样如果列表为空,就会被抛出一个IndexError, 于是就能让爬虫的流程进入到爬虫中间件的process_spider_exception()中 """ print("第%s页出现错误,错误信息:%s" % response.meta["page"], exception) # 这里可以捕获异常信息,也可以有返回值 # 当爬虫运行到start_request时被调用 def process_start_requests(self, start_requests, spider): for r in start_requests: print(r.text) yield r # 当爬虫开始时调用 def spider_opened(self, spider): spider.logger.info('Spider opened: %s' % spider.name)

注意:要在settings配置文件中开启爬虫中间件

七、 模拟登录 1、 cookie

在整个框架运作前,需要一个启动条件,这个启动条件就是start_urls,首先从start_urls的网页发起requests请求,才会有后面的调度器、下载器、爬虫、管道的运转。所以,这里我们可以针对start_urls进行网络请求的start_requests方法进行重写,把我们的cookie给携带进去

注意:必须要使用yield返回,不然没办法运行

import scrapy class ExampleSpider(scrapy.Spider): name = 'example' # allowed_domains = ['example.com'] start_urls = ['www.baidu.com'] # 重写start_request方法,scrapy从这里开始 def start_requests(self): # 添加cookie的第一种方法,直接添加 cookie = " " cookie_dic = {} for i in cookie.split(";"): cookie_dic[i.split("=")[0]] = i.split("=")[1] # 添加cookie的第二种方法:添加头部 headers = { "cookie": "cookie_info", # 使用headers传入cookie时,要在settings中加入COOKIES_ENABLE = True } for url in self.start_urls: yield scrapy.Request(url=url, callback=self.parse, headers=headers) # 添加cookies def parse(self, response): print(response.text) 2、 直接登录

通过传递参数,访问接口,来实现模拟登录:

第一种方法的使用方法:

import scrapy class ExampleSpider(scrapy.Spider): name = 'example' # allowed_domains = ['example.com'] start_urls = ['github.com'] def parse(self, response): # 这里面填写大量的登录参数 post_data = { "username": "lzk", "password": "123456", "time": "123", "sad": "asdsad12", } # 把登录参数传入服务器,验证登录 # 方法一 yield scrapy.FormRequest( url='github.com/session', formdata=post_data, callback=self.parse_login, ) def parse_login(self, response): print(response.text)

第二种方法的使用方法

# -*- coding: utf-8 -*- import scrapy from scrapy import FormRequest, Request class ExampleLoginSpider(scrapy.Spider): name = "login_" # allowed_domains = ["example.webscraping.com"] start_urls = ['example.webscraping.com/user/profile'] login_url = 'example.webscraping.com/places/default/user/login' def start_requests(self): # 重写start_requests方法,用来登录 yield scrapy.Request( self.login_url, callback=self.login ) def login(self,response): formdata = { 'email': 'liushuo@webscraping.com', 'password': '12345678' } yield FormRequest.from_response( response, formdata=formdata, callback=self.parse_login ) def parse_login(self, response): if 'Welcome Liu' in response.text: yield from super().start_requests() # 继承start_requests 的作用,访问要访问的页面 def parse(self, response): print(response.text)

使用from_response方法发送请求,等同于selenium里面的查找表单直接将数据填入表单中,不用考虑加密

八、 分布式爬虫 1、概念

概念:

  • 多台机器对一个项目进行分布联合爬取

作用:

  • 增加工作单位,提升爬取效率

实现:

  • 多台机器共用一个调度器
    • 实现一个公有调度器
      • 首先要保证每台机器都可以进行连接,其次的话要能够进行存储,也就是存储我们爬取的url,就是数据库的存储功能,使用redis
        • 可以把url由爬虫交给引擎,引擎给redis
        • 也可以把url由调度器交给redis
        • 同样也可以在持久化存储中,也由管道把item数据交给redis进行存储
      • 安装
        • pip install scrapy-redis -i pypi.com/simple
2、 用法

在settings配置文件中添加

# 使用scrapy_redis的管道,其为定义好的管道,直接调用就可以 ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300, } # 指定redis地址 REDIS_HOST = '192.168.45.132' # redis服务器地址,我们使用的虚拟机 REDIS_PORT = 6379 # redis端口 # 使用scrapy_redis 的调度器 SCHEDULER = 'scrapy_redis.scheduler.Scheduler' # 去重容器类配置,作用:redis的set集合,来存储请求的指纹数据,从而实现去重的持久化 DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' # 配置调度器是否需要持久化,爬虫结束的时候要不要清空Redis中请求队列和指纹的set集合,要持久化设置为True SCHEDULER_PERSIST = True

在爬虫文件中添加

import scrapy from ..items import TaoCheItem from scrapy_redis.spiders import RedisCrawlSpider from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule # 注意 如果使用的是scrapy.Spider 那么使用redis分布式的时候,就继承 RedisSpider # 如果是CrawlSpider 就继承 RedisCrawlSpider class TaocheSpider(RedisCrawlSpider): name = 'taoche' # allowed_domains = ['taoche.com'] # start_urls = ['changsha.taoche.com/bmw/?page=1'] # 起始的url应该去redis(公共调度器)里面获取 redis_key = 'taoche' # 回去redis里面获取key值为taoche的数据 rules = ( Rule(LinkExtractor(allow=r'/\?page=\d+'), callback='parse_item', follow=True), ) def parse_item(self, response): car_list = response.xpath('//div[@id="container_base"]/ul/li') for car in car_list: lazyimg = car.xpath('./div[1]/div/a/img/@src').extract_first() lazyimg = 'www.558idc.com/yz.html提供,感恩】

本文共计5302个文字,预计阅读时间需要22分钟。

Scrapy框架在Python中如何高效抓取网页数据?

目录 + Scrapy 框架 + 简介 + 1. 介绍 + 2. 介绍 + 3. 环境配置 + 4. 常用命令 + 运行原理 + 4.1 流程图 + 4.2 流程图 + 4.3 组件简介 + 4.4 运行流程 + 二、创建项目 + 1. 修改配置 + 2. 创建项目 + 3. 定义数据 + 4. 编写代码

目录
  • Scrapy 框架
    • 一、 简介
      • 1、 介绍
      • 2、 环境配置
      • 3、 常用命令
      • 4、 运行原理
        • 4.1 流程图
        • 4.2 部件简介
        • 4.3 运行流程
    • 二、 创建项目
      • 1、 修改配置
      • 2、 创建一个项目
      • 3、 定义数据
      • 4、 编写并提取数据
      • 5、 存储数据
      • 6、 运行文件
    • 三、 日志打印
      • 1、 日志信息
      • 2、 logging 模块
    • 四、 全站爬取
      • 1、 使用request排序入队
      • 2、 继承crawlspider
    • 五、 二进制文件
      • 1、 图片下载
    • 六、 middlewares
      • 1、下载中间件
      • 2、 爬虫中间件
    • 七、 模拟登录
      • 1、 cookie
      • 2、 直接登录
    • 八、 分布式爬虫
      • 1、概念
      • 2、 用法

Scrapy 框架 一、 简介 1、 介绍

Scrapy 是一个基于 Twisted 实现的异步处理爬虫框架,该框架使用纯 Python 语言编写。Scrapy 框架应用广泛,常用于数据采集、网络监测,以及自动化测试等

2、 环境配置
  1. 安装 pywin32
    • pip install pywin32
  2. 安装 wheel
    • pip install wheel
  3. 安装 twisted
    • pip install twisted
  4. 安装 scrapy 框架
    • pip install scrapy
3、 常用命令 命令 格式 说明 startproject scrapy startproject <项目名> 创建一个新项目 genspider scrapy genspider <爬虫文件名> <域名> 新建爬虫文件 runspider scrapy runspider <爬虫文件> 运行一个爬虫文件,不需要创建项目 crawl scrapy crawl <spidername> 运行一个爬虫项目,必须要创建项目 list scrapy list 列出项目中所有爬虫文件 view scrapy view <url地址> 从浏览器中打开 url 地址 shell scrapy shell <url地址> 命令行交互模式 settings scrapy settings 查看当前项目的配置信息 4、 运行原理 4.1 流程图 4.2 部件简介
  1. 引擎(Engine)

    引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。

  2. 调度器(Scheduler)

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

  3. 下载器(Downloader)

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

  4. 爬虫(Spiders)

    是开发人员自定义的类,它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器)

  5. 项目管道(Item Pipeline)

    在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作

  6. 下载中间件(Downloader Middlerwares)

    你可以当作是一个可以自定义扩展下载功能的组件。

  7. 爬虫中间件(Spider Middlerwares)

    Scrapy框架在Python中如何高效抓取网页数据?

    位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)

4.3 运行流程
  1. 引擎:Hi!Spider, 你要处理哪一个网站?
  2. Spider:老大要我处理xxxx.com。
  3. 引擎:你把第一个需要处理的URL给我吧。
  4. Spider:给你,第一个URL是xxxxxxx.com。
  5. 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
  6. 调度器:好的,正在处理你等一下。
  7. 引擎:Hi!调度器,把你处理好的request请求给我。
  8. 调度器:给你,这是我处理好的request
  9. 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求
  10. 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)
  11. 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)
  12. Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
  13. 引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
  14. 管道调度器:好的,现在就做!

注意:只有当调度器没有request需要处理时,整个程序才会停止。(对于下载失败的URL,Scrapy也会重新下载。)

二、 创建项目

本次示例是爬取豆瓣

1、 修改配置

LOG_LEVEL = "WARNING" # 设置日志等级 from fake_useragent import UserAgent USER_AGENT = UserAgent().random # 设置请求头 ROBOTSTXT_OBEY = False # 是否遵守 robots 协议,默认为 True ITEM_PIPELINES = { # 开启管道 'myFirstSpider.pipelines.MyfirstspiderPipeline': 300, # 300 为权重, 'myFirstSpider.pipelines.DoubanPipeline': 301, # 数字越大权重越小 } 2、 创建一个项目

在命令行输入:

(scrapy_) D:\programme\Python\scrapy_>scrapy startproject myFirstSpider (scrapy_) D:\programme\Python\scrapy_>cd myFirstSpider (scrapy_) D:\programme\Python\scrapy_\myFirstSpider>scrapy genspider douban "douban.com" 3、 定义数据

定义一个提取的结构化数据(Item)

  1. 打开 myFirstSpider 目录下的 items.py
  2. item 定义结构化的数据字段,用来存储爬取到的数据,有点像python里面的字典,但是提供了一些而外的保护减少错误
  3. 可以通过创建一个 scrapy.Item 类,并且定义类型为 scrapy.Field 的类属性来定义一个 Item (可以理解成类似于ORM的映射关系)
  4. 接下来,创建一个 Douban 类,和构建 item 模型

# Define here the models for your scraped items # # See documentation in: # docs.scrapy.org/en/latest/topics/items.html import scrapy class MyfirstspiderItem(scrapy.Item): # 可以自己创建一个类,但是要继承 scrapy.Item 类 # define the fields for your item here like: # name = scrapy.Field() pass class DoubanItem(scrapy.Item): title = scrapy.Field() # 标题 introduce = scrapy.Field() # 介绍 4、 编写并提取数据

编写爬取网站的 Spider 并提取出结构化数据(Item)

在定义的爬虫文件中写入:

import scrapy from ..items import DoubanItem # 导入定义的格式化数据 class DoubanSpider(scrapy.Spider): name = 'douban' # 爬虫的识别名称,唯一的 # allowed_domains = ['douban.com'] # 允许爬取的范围 # start_urls = ['douban.com/'] # 最初爬取的 url start_urls = ['movie.douban.com/top250'] # 可以自己定义要爬取的 url def parse(self, response): info = response.xpath('//div[@class="info"]') for i in info: # 存放电影信息合集 item = DoubanItem() title = i.xpath("./div[1]/a/span[1]/text()").extract_first() # 获取第一个内容,通过extract方法提取selector对象 introduce = i.xpath("./div[2]/p[1]//text()").extract() # 获取全部内容 introduce = "".join(j.strip() for j in [i.replace("\\xa0", '') for i in introduce]) # 整理信息 item["title"] = title item["introduce"] = introduce # 将获取的数据交给 pipeline yield item 5、 存储数据

编写 Item Pipelines 来存储提取到的 Item (即结构化数据)

# Define your item pipelines here # # Don't forget to add your pipeline to the ITEM_PIPELINES setting # See: docs.scrapy.org/en/latest/topics/item-pipeline.html # useful for handling different item types with a single interface from itemadapter import ItemAdapter class MyfirstspiderPipeline: def process_item(self, item, spider): return item class DoubanPipeline: # 在爬虫文件开始时,运行此函数 def open_spider(self, spider): if spider.name == "douban": # 如果数据是从豆瓣爬虫传进来的 print("爬虫开始运行!") self.fp = open("./douban.txt", "w", encoding="utf-8") def process_item(self, item, spider): if spider.name == "douban": self.fp.write(f"标题:{item['title']}, 信息:{item['introduce']}") # 保存文件 # 爬虫结束的时候运行 def close_spider(self, spider): if spider.name == "douban": print("爬虫结束运行!") self.fp.close() 6、 运行文件

(scrapy_) D:\programme\Python\scrapy_\myFirstSpider>scrapy crawl douban 三、 日志打印 1、 日志信息

日志信息等级:

  • ERROR:错误信息
  • WARNING:警告
  • INFO:一般的信息
  • DEBUG:调试信息

设置日志信息的制定输出

LOG_LEVEL = "ERROR" # 指定日志信息种类 LOG_FILE = "log.txt" # 表示将日志信息写到指定的文件中进行存储 2、 logging 模块

imoprt logging logger = logging.getLogger(__name__) # __name__ 获得项目的文件名 logger.warning(" info ") # 打印要输出的日志信息 四、 全站爬取 1、 使用request排序入队

yield scrapy.Request(url=new_url, callback=self.parse_taoche, meta={"page": page})

参数:

  • url:传递的地址
  • callback:请求后响应数据的处理函数
  • meta:传递数据
    • 每次请求都会携带meta参数
    • 传递给响应
    • 可以通过response.meta \ response.meta["page"]获取

import scrapy, logging from ..items import DetailItem logger = logging.Logger(__name__) class DoubanSpider(scrapy.Spider): name = 'douban' # allowed_domains = ['douban.com'] start_urls = ['movie.douban.com/top250'] def parse(self, response): print(response) info = response.xpath('//div[@class="info"]') for i in info: item_detail = DetailItem() # 详情页的内容 # 存放电影信息合集 title = i.xpath("./div[1]/a/span[1]/text()").extract_first() # 获取第一个内容,通过extract方法提取selector对象 item_detail["title"] = title logger.warning(title) detail_url = i.xpath("./div[1]/a/@href").extract_first() # 获取详情页的url # print(detail_url) yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta=item_detail) # 将请求传递给调度器,重新请求 next_url = response.xpath("//div[@class='paginator']/span[3]/a/@href").extract_first() # 获取下一页的url if next_url: next_url = "movie.douban.com/top250" + next_url # print(next_url) yield scrapy.Request(url=next_url, callback=self.parse, ) # 将请求传递给调度器,重新请求 def parse_detail(self, resp): item = resp.meta # 接收结构化数据 introduce = resp.xpath("//div[@id='link-report']/span[1]/span//text()").extract() # 获取介绍 item["introduce"] = introduce logger.warning(introduce) content = resp.xpath("//div[@id='hot-comments']/div[1]//text()").extract() # 获取评论 item["content"] = content logger.warning(content) yield item 2、 继承crawlspider

Scrapy框架中分为两类爬虫:

  • Spider
  • CrawlSpider:
    • CrawlSpider是Spider的派生类,Spider类的设计原理是指爬取start_url列表中的网页,而CrwalSpider类定义了一些规则来提供跟进链接的方便的机制,从爬取的网页中获取链接并继续爬取的工作更合理

创建方法:

scrapy genspider -t crawl 项目名称 网站

创建后,其显示为

import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class FuhaoSpider(CrawlSpider): name = 'fuhao' # allowed_domains = ['fuhao.com'] start_urls = ['www.phb123.com/renwu/fuhao/shishi_1.html'] rules = ( Rule( LinkExtractor(allow=r'shishi_\d+.html'), # 链接提取器,根据正则规则提取url callback='parse_item', # 指定回调函数 follow=True # 获取的响应页面是否再次经过rules来进行提取url地址 ), ) def parse_item(self, response): print(response.request.url)

Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True)

  • LinkExtractor:链接提取器,根据正则规则提取url地址
  • callback:提取出来的url地址发送请求获取响应,会把响应对象给callback指定的函数进行处理
  • follow:获取的响应页面是否再次经过rules来进行提取url地址

# 匹配豆瓣 start_urls = ['movie.douban.com/top250?start=0&filter='] rules = ( Rule(LinkExtractor(allow=r'?start=\d+&filter='), callback='parse_item', follow=True), )

五、 二进制文件 1、 图片下载

ImagesPipeLine:图片下载的模块

pipeline中,编写代码(已知,item里面传输的是图片的下载地址)

import logging import scrapy from itemadapter import ItemAdapter from scrapy.pipelines.images import ImagesPipeline # 继承ImagesPipeLine class PicPipeLine(ImagesPipeline): # 根据图片地址,发起请求 def get_media_requests(self, item, info): src = item["src"] # item["src] 里面存储的是图片的地址 logging.warning("正在访问图片:", src) yield scrapy.Request(url = src,meta={'item':item}) # 对图片发起请求 # 指定图片的名字 def file_path(self, request, response=None, info=None, *, item=None): item = request.meta['item'] # 接收meta参数 return request.url.split("/")[-1] # 设置文件名字 # 在settings中设置 IMAGES_STORE = "./imags" # 设置图片保存的文件夹 # 返回数据给下一个即将被执行的管道类 def item_completed(self, results, item, info): return item 六、 middlewares 1、下载中间件

更换代理IP,更换Cookies,更换User-Agent,自动重试

在settings.py 中添加

# 建立ip池 PROXY_LIST = []

在middlewares.py中添加

from fake_useragent import UserAgent import random class Spider4DownloaderMiddleware: # 拦截所有请求 def process_request(self, request, spider): # UA 伪装 request.headers["User-Agent"] = UserAgent().random return None # 处理请求,可以篡改响应信息,spider是实例化的爬虫对象 def process_response(self, request, response, spider): bro = spider.bro # 在爬虫对象中创建了的selenium对象 if request.url in spider.model_urls: # print(request.url) # 要篡改request请求的响应对象, response bro.get(request.url) # 执行js代码 bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') # 一拉到底,发现我们滚动条还是在中间位置 bottom = [] # 空列表,表示没有到底部 while not bottom: # bool([]) ==> false not false bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') page_text = bro.page_source # 获取页面内容 # 如果到底,循环结束 bottom = re.findall(r'<div class="load_more_tip" style="display: block;">:-\)已经到最后啦~</div>', page_text) time.sleep(1) if not bottom: try: bro.find_element(By.CSS_SELECTOR, '.load_more_btn').click() # 找到加载更多进行点击 except: bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') return HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request) # body 是要返回的数据 return response # 处理异常,当网络请求失败时,执行此函数 def process_exception(self, request, exception, spider): # 添加代理ip type_ = request.url.split(":")[0] request.meta['proxy'] = f"{type_}://{random.choice(spider.settings.get('PROXY_LIST'))}" return request # 如果ip被封了,就使用代理ip,重新发送请求 # 开始爬虫时执行 def spider_opened(self, spider): spider.logger.info('Spider opened: %s' % spider.name)

设置完下载中间件后,要在settings配置文件中开启

2、 爬虫中间件

爬虫中间件的用法与下载器中间件非常相似,只是它们的作用对象不同。下载器中间件的作用对象是请求request和返回response;爬虫中间件的作用对象是爬虫,更具体地来说,就是写在spiders文件夹下面的各个文件

  1. 当运行到yield scrapy.Request()或者yield item的时候,爬虫中间件的process_spider_output()方法被调用
  2. 当爬虫本身的代码出现了Exception的时候,爬虫中间件的process_spider_exception()方法被调用
  3. 当爬虫里面的某一个回调函数parse_xxx()被调用之前,爬虫中间件的process_spider_input()方法被调用
  4. 当运行到start_requests()的时候,爬虫中间件的process_start_requests()方法被调用

import scrapy class Spider5SpiderMiddleware: # 在下载器中间件处理完成后,马上要进入某个回调函数parse_xxx()前调用 def process_spider_input(self, response, spider): return None # 在爬虫运行yield item或者yield scrapy.Request()的时候调用 def process_spider_output(self, response, result, spider): for item in result: print(result) if isinstance(item, scrapy.Item): # 这里可以对即将被提交给pipeline的item进行各种操作 print(f'item将会被提交给pipeline') yield item # 也可以 yield request,当为yield request时,可以修改请求信息,如meta等 # 当在爬虫程序运行过程中报错时调用 def process_spider_exception(self, response, exception, spider): """ 爬虫里面如果发现了参数错误,就使用raise这个关键字人工抛出一个自定义的异常。在实际爬虫开发中,可以在某些地方故意不使用try ... except捕获异常,而是让异常直接抛出。例如XPath匹配处理的结果,直接读里面的值,不用先判断列表是否为空。这样如果列表为空,就会被抛出一个IndexError, 于是就能让爬虫的流程进入到爬虫中间件的process_spider_exception()中 """ print("第%s页出现错误,错误信息:%s" % response.meta["page"], exception) # 这里可以捕获异常信息,也可以有返回值 # 当爬虫运行到start_request时被调用 def process_start_requests(self, start_requests, spider): for r in start_requests: print(r.text) yield r # 当爬虫开始时调用 def spider_opened(self, spider): spider.logger.info('Spider opened: %s' % spider.name)

注意:要在settings配置文件中开启爬虫中间件

七、 模拟登录 1、 cookie

在整个框架运作前,需要一个启动条件,这个启动条件就是start_urls,首先从start_urls的网页发起requests请求,才会有后面的调度器、下载器、爬虫、管道的运转。所以,这里我们可以针对start_urls进行网络请求的start_requests方法进行重写,把我们的cookie给携带进去

注意:必须要使用yield返回,不然没办法运行

import scrapy class ExampleSpider(scrapy.Spider): name = 'example' # allowed_domains = ['example.com'] start_urls = ['www.baidu.com'] # 重写start_request方法,scrapy从这里开始 def start_requests(self): # 添加cookie的第一种方法,直接添加 cookie = " " cookie_dic = {} for i in cookie.split(";"): cookie_dic[i.split("=")[0]] = i.split("=")[1] # 添加cookie的第二种方法:添加头部 headers = { "cookie": "cookie_info", # 使用headers传入cookie时,要在settings中加入COOKIES_ENABLE = True } for url in self.start_urls: yield scrapy.Request(url=url, callback=self.parse, headers=headers) # 添加cookies def parse(self, response): print(response.text) 2、 直接登录

通过传递参数,访问接口,来实现模拟登录:

第一种方法的使用方法:

import scrapy class ExampleSpider(scrapy.Spider): name = 'example' # allowed_domains = ['example.com'] start_urls = ['github.com'] def parse(self, response): # 这里面填写大量的登录参数 post_data = { "username": "lzk", "password": "123456", "time": "123", "sad": "asdsad12", } # 把登录参数传入服务器,验证登录 # 方法一 yield scrapy.FormRequest( url='github.com/session', formdata=post_data, callback=self.parse_login, ) def parse_login(self, response): print(response.text)

第二种方法的使用方法

# -*- coding: utf-8 -*- import scrapy from scrapy import FormRequest, Request class ExampleLoginSpider(scrapy.Spider): name = "login_" # allowed_domains = ["example.webscraping.com"] start_urls = ['example.webscraping.com/user/profile'] login_url = 'example.webscraping.com/places/default/user/login' def start_requests(self): # 重写start_requests方法,用来登录 yield scrapy.Request( self.login_url, callback=self.login ) def login(self,response): formdata = { 'email': 'liushuo@webscraping.com', 'password': '12345678' } yield FormRequest.from_response( response, formdata=formdata, callback=self.parse_login ) def parse_login(self, response): if 'Welcome Liu' in response.text: yield from super().start_requests() # 继承start_requests 的作用,访问要访问的页面 def parse(self, response): print(response.text)

使用from_response方法发送请求,等同于selenium里面的查找表单直接将数据填入表单中,不用考虑加密

八、 分布式爬虫 1、概念

概念:

  • 多台机器对一个项目进行分布联合爬取

作用:

  • 增加工作单位,提升爬取效率

实现:

  • 多台机器共用一个调度器
    • 实现一个公有调度器
      • 首先要保证每台机器都可以进行连接,其次的话要能够进行存储,也就是存储我们爬取的url,就是数据库的存储功能,使用redis
        • 可以把url由爬虫交给引擎,引擎给redis
        • 也可以把url由调度器交给redis
        • 同样也可以在持久化存储中,也由管道把item数据交给redis进行存储
      • 安装
        • pip install scrapy-redis -i pypi.com/simple
2、 用法

在settings配置文件中添加

# 使用scrapy_redis的管道,其为定义好的管道,直接调用就可以 ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300, } # 指定redis地址 REDIS_HOST = '192.168.45.132' # redis服务器地址,我们使用的虚拟机 REDIS_PORT = 6379 # redis端口 # 使用scrapy_redis 的调度器 SCHEDULER = 'scrapy_redis.scheduler.Scheduler' # 去重容器类配置,作用:redis的set集合,来存储请求的指纹数据,从而实现去重的持久化 DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' # 配置调度器是否需要持久化,爬虫结束的时候要不要清空Redis中请求队列和指纹的set集合,要持久化设置为True SCHEDULER_PERSIST = True

在爬虫文件中添加

import scrapy from ..items import TaoCheItem from scrapy_redis.spiders import RedisCrawlSpider from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule # 注意 如果使用的是scrapy.Spider 那么使用redis分布式的时候,就继承 RedisSpider # 如果是CrawlSpider 就继承 RedisCrawlSpider class TaocheSpider(RedisCrawlSpider): name = 'taoche' # allowed_domains = ['taoche.com'] # start_urls = ['changsha.taoche.com/bmw/?page=1'] # 起始的url应该去redis(公共调度器)里面获取 redis_key = 'taoche' # 回去redis里面获取key值为taoche的数据 rules = ( Rule(LinkExtractor(allow=r'/\?page=\d+'), callback='parse_item', follow=True), ) def parse_item(self, response): car_list = response.xpath('//div[@id="container_base"]/ul/li') for car in car_list: lazyimg = car.xpath('./div[1]/div/a/img/@src').extract_first() lazyimg = 'www.558idc.com/yz.html提供,感恩】