Python中Scrapy框架
Scrapy 框架
一、 简介
1、 介绍
Scrapy 是一个基于 Twisted 实现的异步处理爬虫框架,该框架使用纯 Python 语言编写。Scrapy 框架应用广泛,常用于数据采集、网络监测,以及自动化测试等
2、 环境配置
- 安装 pywin32
pip install pywin32
- 安装 wheel
pip install wheel
- 安装 twisted
pip install twisted
- 安装 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 部件简介
-
引擎(Engine)
引擎
负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。 -
调度器(Scheduler)
用来接受
引擎
发过来的请求, 压入队列中, 并在引擎
再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址 -
下载器(Downloader)
用于下载网页内容, 并将网页内容返回给
EGINE
,下载器是建立在twisted这个高效的异步模型上的 -
爬虫(Spiders)
是开发人员自定义的类,它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给
引擎
,再次进入Scheduler(调度器)
-
项目管道(Item Pipeline)
在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
-
下载中间件(Downloader Middlerwares)
你可以当作是一个可以自定义扩展下载功能的组件。
-
爬虫中间件(Spider Middlerwares)
位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)
4.3 运行流程
引擎
:Hi!Spider
, 你要处理哪一个网站?Spider
:老大要我处理xxxx.com。引擎
:你把第一个需要处理的URL给我吧。Spider
:给你,第一个URL是xxxxxxx.com。引擎
:Hi!调度器
,我这有request请求你帮我排序入队一下。调度器
:好的,正在处理你等一下。引擎
:Hi!调度器
,把你处理好的request请求给我。调度器
:给你,这是我处理好的request引擎
:Hi!下载器,你按照老大的下载中间件
的设置帮我下载一下这个request请求下载器
:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎
告诉调度器
,这个request下载失败了,你记录一下,我们待会儿再下载)引擎
:Hi!Spider
,这是下载好的东西,并且已经按照老大的下载中间件
处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()
这个函数处理的)Spider
:(处理完毕数据之后对于需要跟进的URL),Hi!引擎
,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。引擎
:Hi !管道
我这儿有个item你帮我处理一下!调度器
!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。管道调度器
:好的,现在就做!
注意:只有当
调度器
没有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)
- 打开 myFirstSpider 目录下的 items.py
- item 定义结构化的数据字段,用来存储爬取到的数据,有点像python里面的字典,但是提供了一些而外的保护减少错误
- 可以通过创建一个 scrapy.Item 类,并且定义类型为 scrapy.Field 的类属性来定义一个 Item (可以理解成类似于ORM的映射关系)
- 接下来,创建一个 Douban 类,和构建 item 模型
# Define here the models for your scraped items # # See documentation in: # https://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 = ['http://douban.com/'] # 最初爬取的 url start_urls = ['https://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: https://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 = ['https://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 = "https://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 = ['https://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 = ['https://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文件夹下面的各个文件
- 当运行到
yield scrapy.Request()
或者yield item
的时候,爬虫中间件的process_spider_output()
方法被调用 - 当爬虫本身的代码出现了
Exception
的时候,爬虫中间件的process_spider_exception()
方法被调用 - 当爬虫里面的某一个回调函数
parse_xxx()
被调用之前,爬虫中间件的process_spider_input()
方法被调用 - 当运行到
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 = ['https://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 = ['https://github.com'] def parse(self, response): # 这里面填写大量的登录参数 post_data = { "username": "lzk", "password": "123456", "time": "123", "sad": "asdsad12", } # 把登录参数传入服务器,验证登录 # 方法一 yield scrapy.FormRequest( url='https://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 = ['http://example.webscraping.com/user/profile'] login_url = 'http://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 https://pypi.com/simple
- 首先要保证每台机器都可以进行连接,其次的话要能够进行存储,也就是存储我们爬取的url,就是数据库的存储功能,使用redis
- 实现一个公有调度器
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 = ['https://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 = 'https:' + lazyimg title = car.xpath('./div[2]/a/span/text()').extract_first() resisted_date = car.xpath('./div[2]/p/i[1]/text()').extract_first() mileage = car.xpath('./div[2]/p/i[2]/text()').extract_first() city = car.xpath('./div[2]/p/i[3]/text()').extract_first().replace('\n', '').strip() price = car.xpath('./div[2]/div[1]/i[1]//text()').extract() price = ''.join(price) sail_price = car.xpath('./div[2]/div[1]/i[2]/text()').extract_first() print(lazyimg, title, resisted_date, mileage, city, price, sail_price)
本文来自博客园,作者:Kenny_LZK,转载请注明原文链接:https://www.cnblogs.com/liuzhongkun/p/16148770.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了