2024-10-11 19:35阅读: 27评论: 0推荐: 0

scrapy框架学习笔记

scrapy运行机制

详见Architecture overview

安装

直接pip install scrapy即可

使用

命令行

  • scrapy startproject name命令创建一个新的Scrapy项目
  • scrapy crawl SpiderName命令运行爬虫
  • scrapy runspider SpiderName命令运行脚本。
    更多命令直接查Command line tool

概述

  • 编写Spider,返回Request或自定义的Item对象
  • 所有Request对象都会被scrapy调度请求,并在请求后调用设定的callback函数
  • Item对象则是提取请求回的数据,结构化数据并交由item pipeline处理
  • 定义好Item对象,存储必要的信息(个人认为以文本数据为主,二进制数据应保存链接后续pipeline处理)
  • 定义item pipeline,处理Item对象,例如存储到数据库或文件

编写Spider

  1. project/spider下创建Spider文件(scrapy genspider SpiderName website.com可快速创建示例)
    定义Spider类,必须继承scrapy.Spider类,并且必须指定类参数namescrapy crawl通过这个name来启动爬虫。

  2. 实现start_requests方法,定义爬虫入口,返回一个Request对象。
    如果只是简单的get请求,可以设定类参数start_urls = [url list]以使用默认方法(默认回调是parse方法)

  3. 实现parse(self, response)方法,用于处理请求返回的响应,返回Item对象或者Request对象。
    经常有请求完初始链接后要从响应拿到下一步的链接,这时可以在parse方法中返回一个Request对象,callback指定另一个函数,且可以通过kw_args传递额外参数(如本次处理的数据)给callback。
    返回Item则会交给pipeline处理

  4. Request对象和Response对象Selectors

copy
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
from scrapy.http import Request, JsonRequest ... # 一般的Request请求构建 yield Request( url=url, method="GET", # 默认get,一般不写 headers={}, # 看需要写,Spider middleware好像也能操作 callback=self.parse, # 自定义回调函数 cb_kwargs={"additional_args": args}, ) # post请求 yield Request( url=url, method="POST", headers={"Content-Type": "application/json"}, # 看需要写 callback=self.parse, body=json.dumps(payload), # 注意转成JSON字符串 ) yield JsonRequest( # 省略content-type,基本就是post请求 url=url, data=payload, # 注意这个直接传dict callback=self.parse, )
copy
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
def parse(self, response, if_additional_args): response.text response.body # 取二进制数据,注意不是requests的content response.json() # 如果是json数据,可以直接使用 # scrapy可以直接使用xpath和css选择器,非常方便 for quote in response.xpath("//div[@class='quote']"): # 返回的是SelectorList对象,可以遍历 yield { "quote": quote.xpath("./span[1]/text()").get(), # 注意用get返回第一个结果值 "author": quote.xpath("span[2]/small/text()").get(), "tags": quote.css("a.tag::text").getall(), # 注意用getall返回所有结果的列表 }

定义Item

project/item下定义Item类
Item类必须继承scrapy.Item类,Item其实就是个字典,但是key只能是定义了的字段名

copy
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
class CspProblemItem(scrapy.Item): # contest info contest_id = scrapy.Field() contest_title = scrapy.Field() contest_date = scrapy.Field() # problem info title = scrapy.Field() description_url = scrapy.Field() description_filepath = scrapy.Field() description = scrapy.Field() attachment_urls = scrapy.Field() # 最后下载的文件一般不保存在Item字段里, attachment_filepaths = scrapy.Field() # 而是记录文件的url和路径,保存后记录在Item里 # flags to indicate whether the problem has been processed done = scrapy.Field()

编写pipeline

  • project/pipeline下定义pipeline类,不需继承,要实现process_item(self, item, spider)方法
  • 启用pipeline需要在settings.py中设置ITEM_PIPELINES,将需要的pipeline类加上,并赋上priority(越小优先级越高)
  • 每个pipeline都必须返回自己处理的item,item之后会流经其它pipeline
  • pipeline处理处理Item时,该Item就会被锁定该pipeline中直到被return
  • 对于不需要的Item对象可以丢弃,通过raise DropItem("drop item info")丢弃
  • item在这里其实是鸭子类型,scrapy提供了ItemAdapter来统一它们
copy
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
# 因为Item是鸭子类型,在使用时需要判断类型和字段 from itemadapter import ItemAdapter class MyPipeline: def process_item(self, item, spider): adapter = ItemAdapter(item) adapter = ItemAdapter(item) adapter.get("key") # 其实和dict.get()是一样的 adapter.is_item_class(MyItem) # 用于判断是否是需要的Item类型 if adapter.get("done"): # 对于非目标对象,要把Item返回 return item
  • 有时需要下载item里的url,直接使用response的返回值,使用callback就很不方便,这时就要用到deferred
copy
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
# https://docs.scrapy.org/en/latest/topics/coroutines.html#inline-requests from itemadapter import ItemAdapter from scrapy.exceptions import DropItem from scrapy import Request from scrapy.http.request import NO_CALLBACK from scrapy.utils.defer import maybe_deferred_to_future from twisted.internet.defer import DeferredList class MyPipeline: async def process_item(self, item, spider): # 注意要使用async adapter = ItemAdapter(item) # 单个请求 request = Request(adapter["url"], callback=NO_CALLBACK) # 不使用callback response = await maybe_deferred_to_future( spider.crawler.engine.download(request) ) if response.status != 200: # 一般要判断一下下载是否成功,并做一些处理 raise DropItem(f"Could not download {adapter['url']}") # 多个请求,就是比单个的多了DeferredList包装 deferred_list = [] for url in url_list_: request = Request(url, callback=NO_CALLBACK) deferred = spider.crawler.engine.download(request) deferred_list.append(deferred) result = await maybe_deferred_to_future(DeferredList(deferred_list)) # result变量将包含一个列表,该列表中的每个元素是一个元组,表示每个Deferred的结果。这个元组通常有两种形式: # (True, result):如果对应的Deferred成功完成,result是回调链最终返回的结果。 # (False, failure):如果对应的Deferred因错误而终止,failure是一个Failure实例,代表发生的异常。 for i, (success, response) in enumerate(result): if not success: continue file_path.write_bytes(response.body)

关于settings.py

  • 如果全局使用固定的cookie(在浏览器复制),需要在settings.py中注释修改DEFAULT_REQUEST_HEADERS,并且将COOKIES_ENABLED设置为False,否则这里的headers不能使用到cookie。或者也可以在middleware中进行自定义
  • 在settings可以定义自己需要的任何参数,使用Spider对象的spider.settings["key"]即可取到参数值

补充FilesPipeline

内置的媒体资源下载Pipeline,相较于自己实现,它内置有缓存策略等等,也不需要自己构建deferred list。两种方式,选择自己喜欢的就好。

使用FilesPipeline,必须在settings.py定义文件存储路径FILES_STORE

继承scrapy.pipelines.files.FilesPipeline主要override三个方法:

  • file_path(self, request, response=None, info=None, *, item=None):该方法返回了文件的文件名
  • get_media_requests(self, item, info):该方法返回List[Request],根据需要从item里获取请求url并发起请求
  • item_completed(self, results, item, info):该方法定义了文件下载完成后进行的操作,然后返回item
copy
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
# 使用内置的FilesPipeline替代我的AttachmentPipeline # 启用FilesPipeline时必须设置FILES_STORE = "/path/to/valid/dir" # 之后代码中的路径都应是相对路径 from scrapy.pipelines.files import FilesPipeline class AttachmentFilesPipeline(FilesPipeline): """extract attachment urls(zip and images) and download attachment""" def file_path(self, request, response=None, info=None, *, item=None): """use the url to extract file name""" adapter = ItemAdapter(item) file_name = request.url.split("/")[-1] return f"{adapter['description_filepath']}/attachment/{file_name}" def get_media_requests(self, item, info): """extract attachment urls and add them to request""" adapter = ItemAdapter(item) if not adapter.get("description"): # 未经ProblemPipeline处理 return [] if adapter.get("done"): # 已经处理过 return [] description = adapter["description"] attachment_related_urls = re.findall("/staticdata/down/.*?.zip", description) # extract attachment url image_related_urls = re.findall('src="(/staticdata/.*?)"', description) # extract image urls attachment_related_urls.extend(image_related_urls) # 后续要替换原文,所以需要保存提取出来的原文路径 adapter["attachment_urls"] = attachment_related_urls for url in attachment_related_urls: url = urljoin("https://sim.csp.thusaac.com", url) yield Request(url, callback=NO_CALLBACK) def item_completed(self, results, item, info): """replace path in description""" """results结构见https://docs.scrapy.org/en/latest/topics/media-pipeline.html#scrapy.pipelines.files.FilesPipeline.get_media_requests""" adapter = ItemAdapter(item) for i, (success, result) in enumerate(results): if not success: continue # replace path in the description related_path = Path(result["path"]).relative_to(adapter["description_filepath"]) adapter["description"] = adapter["description"].replace( adapter["attachment_urls"][i], str(related_path) ) adapter["done"] = True # 标记已处理 return item """ 总结:FilesPipeline的使用和我写的AttachmentPipeline相似, 只是说把一个方法拆成了两部分,且FilesPipeline对下载的内容进行了管理,更灵活 """

That's all

其实还有middleware等可以定制更丰富的功能,这些都可以直接查询官方文档,根据自己需要进行定制

本文作者:faf4r

本文链接:https://www.cnblogs.com/faf4r/p/18459114

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   faf4r  阅读(27)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起