Scrapy的Pyppeteer集成
项目描述
Scrapy的Pyppeteer集成
此项目提供了一个Scrapy下载处理程序,该处理程序使用Pyppeteer执行请求。它可以用于处理需要JavaScript的页面。此包不会干扰Scrapy的正常工作流程,如请求调度或项目处理。
动机
在发布版本2.0之后,该版本包含部分协程语法支持和实验性的asyncio支持,Scrapy允许集成基于asyncio的项目,如Pyppeteer。
要求
- Python 3.6+
- Scrapy 2.0+
- Pyppeteer 0.0.23+
安装
$ pip install scrapy-pyppeteer
配置
通过DOWNLOAD_HANDLERS
替换默认的http
和https
下载处理程序
DOWNLOAD_HANDLERS = {
"http": "scrapy_pyppeteer.handler.ScrapyPyppeteerDownloadHandler",
"https": "scrapy_pyppeteer.handler.ScrapyPyppeteerDownloadHandler",
}
注意,ScrapyPyppeteerDownloadHandler
类继承自默认的http/https
处理程序,并且它将仅对明确标记的请求使用Pyppeteer(有关详细信息,请参阅“基本用法”部分)。
另外,请确保安装基于asyncio的Twisted反应器
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
scrapy-pyppeteer
接受以下设置
-
PYPPETEER_LAUNCH_OPTIONS
(类型dict
,默认{}
)一个字典,包含在启动浏览器时要传递的选项。请参阅pyppeteer.launcher.launch的文档
-
PYPPETEER_NAVIGATION_TIMEOUT
(类型Optional[int]
,默认None
)Pyppeteer请求页面时使用的默认超时时间(以毫秒为单位)。如果设置为
None
或未设置,则使用默认值(写作本文时为30000毫秒)。有关详细信息,请参阅pyppeteer.page.Page.setDefaultNavigationTimeout文档。 -
PYPPETEER_PAGE_COROUTINE_TIMEOUT
(类型Optional[Union[int, float]]
,默认None
)使用
页面协程
(例如waitForSelector
或waitForXPath
)时传递的默认超时时间(以毫秒为单位)。如果设置为None
或未设置,则使用默认值(写作本文时为30000毫秒)。
基本用法
将pyppeteer
的Request.meta键设置,以便使用Pyppeteer下载请求
import scrapy
class AwesomeSpider(scrapy.Spider):
name = "awesome"
def start_requests(self):
# GET request
yield scrapy.Request("https://httpbin.org/get", meta={"pyppeteer": True})
# POST request
yield scrapy.FormRequest(
url="https://httpbin.org/post",
formdata={"foo": "bar"},
meta={"pyppeteer": True},
)
def parse(self, response):
# 'response' contains the page as seen by the browser
yield {"url": response.url}
页面协程
可以在pyppeteer_page_coroutines
的Request.meta键中传递一个排序的可迭代对象(例如 list
、tuple
或 dict
),以便在返回最终 Response
到回调函数之前在 Page
上等待请求协程。
当您需要在页面上执行某些操作(如滚动或点击链接)并将所有操作视为单个Scrapy Response(包含最终结果)时,这很有用。
支持的操作
-
scrapy_pyppeteer.page.PageCoroutine(method: str, *args, **kwargs)
:表示在
pyppeteer.page.Page
对象上等待的协程,例如 "click"、"screenshot"、"evaluate" 等。method
应为协程的名称,*args
和**kwargs
是传递给函数调用的参数。协程结果将存储在
PageCoroutine.result
属性中例如,
PageCoroutine("screenshot", options={"path": "quotes.png", "fullPage": True})
产生与以下相同的效果
# 'page' is a pyppeteer.page.Page object await page.screenshot(options={"path": "quotes.png", "fullPage": True})
-
scrapy_pyppeteer.page.NavigationPageCoroutine(method: str, *args, **kwargs)
:PageCoroutine
的子类。它等待导航事件:当您知道协程将触发导航事件时使用此功能,例如在点击链接时。这将强制使用Page.waitForNavigation()
调用,并使用asyncio.gather
包装,正如在Pyppeteer文档中建议的那样。例如,
NavigationPageCoroutine("click", selector="a")
产生与以下相同的效果
# 'page' is a pyppeteer.page.Page object await asyncio.gather( page.waitForNavigation(), page.click(selector="a"), )
在回调函数中接收Page对象
将pyppeteer.page.Page
指定为回调参数的类型会导致相应的 Page
对象被注入到回调函数中。为了能够在提供的 Page
对象上等待协程,回调函数需要定义为协程函数(async def
)。
import scrapy
import pyppeteer
class AwesomeSpiderWithPage(scrapy.Spider):
name = "page"
def start_requests(self):
yield scrapy.Request("https://example.org", meta={"pyppeteer": True})
async def parse(self, response, page: pyppeteer.page.Page):
title = await page.title() # "Example Domain"
yield {"title": title}
await page.close()
注意
- 为了避免内存问题,建议手动通过等待
Page.close
协程来关闭页面。 - 在
Page
对象上等待协程产生的任何网络操作(如goto
、goBack
等)将直接由Pyppeteer执行,绕过Scrapy请求工作流程(调度器、中间件等)。
示例
点击链接,将结果页面保存为PDF
import scrapy
from scrapy_pyppeteer.page import PageCoroutine, NavigationPageCoroutine
class ClickAndSavePdfSpider(scrapy.Spider):
name = "pdf"
def start_requests(self):
yield scrapy.Request(
url="https://example.org",
meta=dict(
pyppeteer=True,
pyppeteer_page_coroutines={
"click": NavigationPageCoroutine("click", selector="a"),
"pdf": PageCoroutine("pdf", options={"path": "/tmp/file.pdf"}),
},
),
)
def parse(self, response):
pdf_bytes = response.meta["pyppeteer_page_coroutines"]["pdf"].result
with open("iana.pdf", "wb") as fp:
fp.write(pdf_bytes)
yield {"url": response.url} # response.url is "https://www.iana.org/domains/reserved"
在无限滚动页面上向下滚动,截取整个页面的屏幕截图
import scrapy
import pyppeteer
from scrapy_pyppeteer.page import PageCoroutine
class ScrollSpider(scrapy.Spider):
name = "scroll"
def start_requests(self):
yield scrapy.Request(
url="http://quotes.toscrape.com/scroll",
meta=dict(
pyppeteer=True,
pyppeteer_page_coroutines=[
PageCoroutine("waitForSelector", "div.quote"),
PageCoroutine("evaluate", "window.scrollBy(0, document.body.scrollHeight)"),
PageCoroutine("waitForSelector", "div.quote:nth-child(11)"), # 10 per page
PageCoroutine("screenshot", options={"path": "quotes.png", "fullPage": True}),
],
),
)
def parse(self, response):
return {"quote_count": len(response.css("div.quote"))}
致谢
本项目灵感来源于