Scrapy的Playwright集成
项目描述
scrapy-playwright: Scrapy的Playwright集成
一个Scrapy下载处理器,它使用Python的Playwright执行请求。它可以用来处理需要JavaScript(以及其他一些功能)的页面,同时遵守常规的Scrapy工作流程(即不干扰请求调度、项目处理等)。
要求
在版本2.0发布后,该版本包括协程语法支持和asyncio支持,Scrapy允许集成基于asyncio
的项目,如Playwright
。
最低要求版本
- Python >= 3.8
- Scrapy >= 2.0 (!= 2.4.0)
- Playwright >= 1.15
安装
scrapy-playwright
可在PyPI上找到,并可以使用pip
进行安装
pip install scrapy-playwright
playwright
被定义为依赖项,因此它会自动安装,但是可能需要安装将使用的特定浏览器
playwright install
还可以只安装可用浏览器的一个子集
playwright install firefox chromium
更新日志
请参阅更新日志文档。
激活
下载处理器
通过DOWNLOAD_HANDLERS
替换默认的http
和/或https
下载处理器。
# settings.py
DOWNLOAD_HANDLERS = {
"http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
"https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}
请注意,ScrapyPlaywrightDownloadHandler
类继承自默认的http/https
处理器。除非明确标记(见基本用法),否则请求将由常规Scrapy下载处理器处理。
Twisted反应器
# settings.py
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
自Scrapy 2.7以来,这是新项目的默认设置。
基本用法
将playwright
Request.meta键设置为使用Playwright下载请求
import scrapy
class AwesomeSpider(scrapy.Spider):
name = "awesome"
def start_requests(self):
# GET request
yield scrapy.Request("https://httpbin.org/get", meta={"playwright": True})
# POST request
yield scrapy.FormRequest(
url="https://httpbin.org/post",
formdata={"foo": "bar"},
meta={"playwright": True},
)
def parse(self, response, **kwargs):
# 'response' contains the page as seen by the browser
return {"url": response.url}
关于User-Agent头部的说明
默认情况下,发出的请求包含由Scrapy设置的User-Agent
(通过USER_AGENT
或DEFAULT_REQUEST_HEADERS
设置或通过Request.headers
属性)。这可能导致一些网站以意外的方式反应,例如如果用户代理与运行的浏览器不匹配。如果您希望使用您使用的特定浏览器默认发送的User-Agent
,请将Scrapy用户代理设置为None
。
Windows支持
通过在单独的线程中运行Playwright以使用ProactorEventLoop
,可以实现Windows支持。这是必要的,因为无法在Scrapy爬虫相同的asyncio事件循环中运行Playwright。
- Playwright在子进程中运行驱动程序。来源:Playwright仓库。
- "在Windows上,默认的事件循环
ProactorEventLoop
支持子进程,而SelectorEventLoop
不支持"。来源:Python文档。 - Twisted的
asyncio
反应器需要SelectorEventLoop
。来源:Twisted仓库。
支持的设置
PLAYWRIGHT_BROWSER_TYPE
类型 str
,默认 "chromium"
。
要启动的浏览器类型,例如chromium
、firefox
、webkit
。
PLAYWRIGHT_BROWSER_TYPE = "firefox"
PLAYWRIGHT_LAUNCH_OPTIONS
类型 dict
,默认 {}
一个字典,包含在启动浏览器时要传递的选项作为关键字参数。有关支持的参数列表,请参阅BrowserType.launch
文档。
PLAYWRIGHT_LAUNCH_OPTIONS = {
"headless": False,
"timeout": 20 * 1000, # 20 seconds
}
PLAYWRIGHT_CDP_URL
类型 Optional[str]
,默认 None
通过BrowserType.connect_over_cdp
连接到远程Chromium浏览器的端点,使用Chrome DevTools Protocol。
PLAYWRIGHT_CDP_URL = "http://localhost:9222"
如果使用此设置
- 所有非持久上下文都将创建在连接的远程浏览器上
PLAYWRIGHT_LAUNCH_OPTIONS
设置被忽略PLAYWRIGHT_BROWSER_TYPE
设置必须设置为不同于"chromium"的值
此设置不能与PLAYWRIGHT_CONNECT_URL
同时使用
PLAYWRIGHT_CDP_KWARGS
类型 dict[str, Any]
,默认 {}
当使用PLAYWRIGHT_CDP_URL
时,传递给BrowserType.connect_over_cdp
的附加关键字参数。始终忽略endpoint_url
键,使用PLAYWRIGHT_CDP_URL
代替。
PLAYWRIGHT_CDP_KWARGS = {
"slow_mo": 1000,
"timeout": 10 * 1000
}
PLAYWRIGHT_CONNECT_URL
类型 Optional[str]
,默认 None
要使用BrowserType.connect
连接的远程Playwright浏览器实例的URL。
来自上游Playwright文档
当通过Node.js中的
BrowserType.launchServer
连接到另一个浏览器时,主版本和副版本需要与客户端版本匹配(1.2.3 → 兼容1.2.x)。
PLAYWRIGHT_CONNECT_URL = "ws://localhost:35477/ae1fa0bc325adcfd9600d9f712e9c733"
如果使用此设置
- 所有非持久上下文都将创建在连接的远程浏览器上
PLAYWRIGHT_LAUNCH_OPTIONS
设置被忽略
此设置不能与PLAYWRIGHT_CDP_URL
同时使用
PLAYWRIGHT_CONNECT_KWARGS
类型 dict[str, Any]
,默认 {}
当使用PLAYWRIGHT_CONNECT_URL
时,传递给BrowserType.connect
的额外关键字参数。键ws_endpoint
始终被忽略,而是使用PLAYWRIGHT_CONNECT_URL
。
PLAYWRIGHT_CONNECT_KWARGS = {
"slow_mo": 1000,
"timeout": 10 * 1000
}
PLAYWRIGHT_CONTEXTS
类型dict[str, dict]
,默认{}
一个字典,用于定义启动时要创建的浏览器上下文。它应该是(名称,关键字参数)的映射。
PLAYWRIGHT_CONTEXTS = {
"foobar": {
"context_arg1": "value",
"context_arg2": "value",
},
"default": {
"context_arg1": "value",
"context_arg2": "value",
},
"persistent": {
"user_data_dir": "/path/to/dir", # will be a persistent context
"context_arg1": "value",
},
}
有关更多信息,请参阅浏览器上下文部分。有关Browser.new_context
的文档。
PLAYWRIGHT_MAX_CONTEXTS
类型Optional[int]
,默认None
允许的最大并发Playwright上下文数量。如果没有设置或为None
,则不强制限制。有关更多信息,请参阅最大并发上下文计数部分。
PLAYWRIGHT_MAX_CONTEXTS = 8
PLAYWRIGHT_DEFAULT_NAVIGATION_TIMEOUT
类型Optional[float]
,默认None
Playwright请求页面时使用的超时时间,以毫秒为单位。如果为None
或未设置,则使用默认值(写作时的30000毫秒)。请参阅BrowserContext.set_default_navigation_timeout的文档。
PLAYWRIGHT_DEFAULT_NAVIGATION_TIMEOUT = 10 * 1000 # 10 seconds
PLAYWRIGHT_PROCESS_REQUEST_HEADERS
类型Optional[Union[Callable, str]]
,默认scrapy_playwright.headers.use_scrapy_headers
一个函数(或函数的路径),用于处理Playwright请求,并返回一个包含要覆盖的头的字典。支持协程函数(async def
)。
此函数至少会被每个Scrapy请求调用一次,但如果Playwright生成更多请求(例如检索资产如图像或脚本),则可能还会被调用更多次。
函数必须返回一个Dict[str, str]
对象,并接收以下三个关键字参数
- browser_type_name: str
- playwright_request: playwright.async_api.Request
- scrapy_request_data: dict
* method: str
* url: str
* headers: scrapy.http.headers.Headers
* body: Optional[bytes]
* encoding: str
默认函数(scrapy_playwright.headers.use_scrapy_headers
)试图模仿Scrapy的导航请求行为,即用Scrapy请求的值覆盖头。对于非导航请求(例如图像、样式表、脚本等),只有User-Agent
头被覆盖,以保持一致性。
设置PLAYWRIGHT_PROCESS_REQUEST_HEADERS=None
将使Playwright完全控制,即忽略Scrapy请求的头,只发送Playwright设置的头。请注意,在这种情况下,通过Request.headers
属性或由Scrapy组件设置的头部(包括通过Request.cookies
属性设置的cookie)将被忽略。
示例
async def custom_headers(
*,
browser_type_name: str,
playwright_request: playwright.async_api.Request,
scrapy_request_data: dict,
) -> Dict[str, str]:
headers = await playwright_request.all_headers()
scrapy_headers = scrapy_request_data["headers"].to_unicode_dict()
headers["Cookie"] = scrapy_headers.get("Cookie")
return headers
PLAYWRIGHT_PROCESS_REQUEST_HEADERS = custom_headers
已弃用的参数处理
在版本0.0.40及之前,参数以位置方式传递给函数,并且只传递Scrapy头,而不是包含Scrapy请求数据的字典。自版本0.0.41起已弃用,并且将根据弃用策略最终删除对此种处理参数的支持。
传递的参数
- browser_type: str
- playwright_request: playwright.async_api.Request
- scrapy_headers: scrapy.http.headers.Headers
示例
def custom_headers(
browser_type: str,
playwright_request: playwright.async_api.Request,
scrapy_headers: scrapy.http.headers.Headers,
) -> dict:
if browser_type == "firefox":
return {"User-Agent": "foo"}
return {"User-Agent": "bar"}
PLAYWRIGHT_PROCESS_REQUEST_HEADERS = custom_headers
PLAYWRIGHT_RESTART_DISCONNECTED_BROWSER
类型bool
,默认True
如果浏览器断开连接,浏览器是否会重新启动,例如,如果本地浏览器崩溃或远程连接超时。通过监听disconnected
Browser事件来实现,因此它不适用于持久上下文,因为BrowserType.launch_persistent_context直接返回上下文。
PLAYWRIGHT_MAX_PAGES_PER_CONTEXT
类型int
,默认值是Scrapy的CONCURRENT_REQUESTS
设置值
每个上下文中允许的并发 Playwright 页面的最大数量。有关未关闭页面的说明,请参阅注意事项。
PLAYWRIGHT_MAX_PAGES_PER_CONTEXT = 4
PLAYWRIGHT_ABORT_REQUEST
类型 Optional[Union[Callable, str]]
,默认 None
一个接收 playwright.async_api.Request
对象的谓词函数(或函数的路径),如果请求应该被中止,则必须返回 True
,否则返回 False
。支持协程函数(async def
)。
注意,所有请求都将出现在 DEBUG 级别日志中,但是中止请求将没有相应的响应日志行。中止请求会在 playwright/request_count/aborted
工作统计项中计数。
def should_abort_request(request):
return (
request.resource_type == "image"
or ".jpg" in request.url
)
PLAYWRIGHT_ABORT_REQUEST = should_abort_request
关于设置的通用说明
对于接受对象路径作为字符串的设置,只有在使用 Scrapy≥2.4 时才支持传递可调用对象。在较早版本中,仅支持字符串。
支持的 Request.meta
键
playwright
类型 bool
,默认 False
如果设置为评估为 True
的值,则请求将由 Playwright 处理。
return scrapy.Request("https://example.org", meta={"playwright": True})
playwright_context
类型 str
,默认 "default"
要用于下载请求的上下文名称。有关更多信息,请参阅浏览器上下文部分。
return scrapy.Request(
url="https://example.org",
meta={
"playwright": True,
"playwright_context": "awesome_context",
},
)
playwright_context_kwargs
类型 dict
,默认 {}
一个字典,包含在创建新上下文时使用的关键字参数。如果不存在具有 playwright_context
元键中指定的名称的上下文,则会使用这些关键字参数。有关更多信息,请参阅浏览器上下文部分。
return scrapy.Request(
url="https://example.org",
meta={
"playwright": True,
"playwright_context": "awesome_context",
"playwright_context_kwargs": {
"ignore_https_errors": True,
},
},
)
playwright_include_page
类型 bool
,默认 False
如果设置为 True
,则用于下载请求的 Playwright 页面 将在回调中可用,位置为 response.meta['playwright_page']
。如果设置为 False
(或未设置),则页面将在处理请求后立即关闭。
重要!
此元键完全可选,不是页面加载或执行任何异步操作(特别是不是应用 PageMethod
对象)所必需的。仅在需要访问回调中处理响应的页面对象时使用它。
有关更多信息和建议,请参阅在回调中接收页面对象。
return scrapy.Request(
url="https://example.org",
meta={"playwright": True, "playwright_include_page": True},
)
playwright_page_event_handlers
类型 Dict[Str, Callable]
,默认 {}
要附加到页面事件的处理器字典。请参阅处理页面事件。
playwright_page_init_callback
类型 Optional[Union[Callable, str]]
,默认 None
一个要为新创建的页面调用的协程函数(async def
)。在附加页面事件处理器和设置内部路由处理之后、发出任何请求之前调用。它接收 Playwright 页面和 Scrapy 请求作为位置参数。对于初始化代码很有用。如果请求的页面已经存在(例如,通过传递 playwright_page
),则忽略。
async def init_page(page, request):
await page.add_init_script(path="./custom_script.js")
class AwesomeSpider(scrapy.Spider):
def start_requests(self):
yield scrapy.Request(
url="https://httpbin.org/headers",
meta={
"playwright": True,
"playwright_page_init_callback": init_page,
},
)
重要!
scrapy-playwright
使用 Page.route
和 Page.unroute
,避免使用这些方法,除非您确切知道自己在做什么。
playwright_page_methods
类型 Iterable[PageMethod]
,默认 ()
一个指示在返回最终响应之前在页面上执行的操作的 scrapy_playwright.page.PageMethod
对象的可迭代对象。请参阅在页面上执行操作。
playwright_page
类型 Optional[playwright.async_api.Page]
,默认 None
用于下载请求的 Playwright 页面。如果没有指定,则每个请求都会创建一个新的页面。此键可以与 playwright_include_page
一起使用,以使用同一页面进行请求链。例如
from playwright.async_api import Page
def start_requests(self):
yield scrapy.Request(
url="https://httpbin.org/get",
meta={"playwright": True, "playwright_include_page": True},
)
def parse(self, response, **kwargs):
page: Page = response.meta["playwright_page"]
yield scrapy.Request(
url="https://httpbin.org/headers",
callback=self.parse_headers,
meta={"playwright": True, "playwright_page": page},
)
playwright_page_goto_kwargs
类型 dict
,默认 {}
一个字典,包含要传递给页面goto
方法的键值参数,用于导航到URL。如果存在url
键,则忽略它,而使用请求URL代替。
return scrapy.Request(
url="https://example.org",
meta={
"playwright": True,
"playwright_page_goto_kwargs": {
"wait_until": "networkidle",
},
},
)
playwright_security_details
类型Optional[dict]
,只读
包含有关给定响应的安全信息的字典。仅适用于HTTPS请求。可以通过response.meta['playwright_security_details']
在回调中访问。
def parse(self, response, **kwargs):
print(response.meta["playwright_security_details"])
# {'issuer': 'DigiCert TLS RSA SHA256 2020 CA1', 'protocol': 'TLS 1.3', 'subjectName': 'www.example.org', 'validFrom': 1647216000, 'validTo': 1678838399}
playwright_suggested_filename
类型Optional[str]
,只读
当响应是下载的二进制内容(例如PDF文件)时,Download.suggested_filename
属性的值。仅适用于仅导致下载的响应。可以通过response.meta['playwright_suggested_filename']
在回调中访问。
def parse(self, response, **kwargs):
print(response.meta["playwright_suggested_filename"])
# 'sample_file.pdf'
在回调中接收Page对象
对于请求,在playwright_include_page
元键中指定一个评估为True
的值,将导致相应的playwright.async_api.Page
对象在请求回调中的playwright_page
元键中可用。为了能够在提供的Page
对象上等待协程,回调需要定义为协程函数(async def
)。
注意
请谨慎使用,并且只有当您确实需要在回调中使用Page对象时才使用它。如果页面对象在不再需要后没有正确关闭,蜘蛛作业可能会因为PLAYWRIGHT_MAX_PAGES_PER_CONTEXT
设置的限制而卡住。
from playwright.async_api import Page
import scrapy
class AwesomeSpiderWithPage(scrapy.Spider):
name = "page_spider"
def start_requests(self):
yield scrapy.Request(
url="https://example.org",
callback=self.parse_first,
meta={"playwright": True, "playwright_include_page": True},
errback=self.errback_close_page,
)
def parse_first(self, response):
page: Page = response.meta["playwright_page"]
return scrapy.Request(
url="https://example.com",
callback=self.parse_second,
meta={"playwright": True, "playwright_include_page": True, "playwright_page": page},
errback=self.errback_close_page,
)
async def parse_second(self, response):
page: Page = response.meta["playwright_page"]
title = await page.title() # "Example Domain"
await page.close()
return {"title": title}
async def errback_close_page(self, failure):
page: Page = failure.request.meta["playwright_page"]
await page.close()
备注
- 当传递
playwright_include_page=True
时,请确保在不再使用页面时始终关闭页面。建议设置请求回退以确保即使在请求失败的情况下也关闭页面(如果playwright_include_page=False
,页面在遇到异常时自动关闭)。这很重要,因为打开的页面计入由PLAYWRIGHT_MAX_PAGES_PER_CONTEXT
设置的限制,如果达到限制且页面无限期打开,则爬虫可能会冻结。 - 定义回调为
async def
仅当您需要等待事物时才是必要的,如果您只是需要将Page对象从一个回调传递到另一个回调(请参阅上面的示例),则不是必要的。 - 在Page对象上等待协程(如
goto
、go_back
等)产生的任何网络操作都将由Playwright直接执行,绕过Scrapy请求工作流程(调度器、中间件等)。
浏览器上下文
可以在启动时通过PLAYWRIGHT_CONTEXTS
设置定义要启动的多个浏览器上下文。
为请求选择特定的上下文
在playwright_context
元键中传递所需上下文名称。
yield scrapy.Request(
url="https://example.org",
meta={"playwright": True, "playwright_context": "first"},
)
默认上下文
如果请求没有通过playwright_context
元键显式指定上下文,则默认使用名为default
的通用上下文。此default
上下文也可以通过启动时的PLAYWRIGHT_CONTEXTS
设置进行自定义。
持久上下文
通过传递user_data_dir
关键字参数来启动上下文作为持久上下文。另请参阅BrowserType.launch_persistent_context
。
请注意,持久上下文独立于主浏览器实例启动,因此PLAYWRIGHT_LAUNCH_OPTIONS
设置中传递的关键字参数不适用。
在爬取过程中创建上下文
如果 playwright_context
元键中指定的上下文不存在,它将被创建。您可以在 playwright_context_kwargs
元键中指定要传递给 Browser.new_context
的关键字参数
yield scrapy.Request(
url="https://example.org",
meta={
"playwright": True,
"playwright_context": "new",
"playwright_context_kwargs": {
"java_script_enabled": False,
"ignore_https_errors": True,
"proxy": {
"server": "http://myproxy.com:3128",
"username": "user",
"password": "pass",
},
},
},
)
请注意,如果已存在具有指定名称的上下文,则将使用该上下文,并且忽略 playwright_context_kwargs
。
在爬取过程中关闭上下文
在 您的回调中接收到页面对象 后,您可以通过对应的 Page.context
属性访问上下文,并等待其 close
。
def parse(self, response, **kwargs):
yield scrapy.Request(
url="https://example.org",
callback=self.parse_in_new_context,
errback=self.close_context_on_error,
meta={
"playwright": True,
"playwright_context": "awesome_context",
"playwright_include_page": True,
},
)
async def parse_in_new_context(self, response):
page = response.meta["playwright_page"]
title = await page.title()
await page.close()
await page.context.close()
return {"title": title}
async def close_context_on_error(self, failure):
page = failure.request.meta["playwright_page"]
await page.close()
await page.context.close()
避免关闭上下文时的竞态条件和内存泄漏
确保在关闭上下文之前关闭页面。有关更多信息,请参阅 此评论,位于 #191。
最大并发上下文计数
为 PLAYWRIGHT_MAX_CONTEXTS
设置指定一个值以限制并发上下文数量。谨慎使用:如果不再使用上下文后没有关闭它们,则可能会阻塞整个爬取(请参阅 本节 以动态关闭上下文)。请确保定义一个错误回调,以便即使在出现错误的情况下也能关闭上下文。
代理支持
通过在 PLAYWRIGHT_LAUNCH_OPTIONS
设置中指定 proxy
键,在浏览器级别支持代理
from scrapy import Spider, Request
class ProxySpider(Spider):
name = "proxy"
custom_settings = {
"PLAYWRIGHT_LAUNCH_OPTIONS": {
"proxy": {
"server": "http://myproxy.com:3128",
"username": "user",
"password": "pass",
},
}
}
def start_requests(self):
yield Request("http://httpbin.org/get", meta={"playwright": True})
def parse(self, response, **kwargs):
print(response.text)
代理也可以通过 PLAYWRIGHT_CONTEXTS
设置在上下文级别设置
PLAYWRIGHT_CONTEXTS = {
"default": {
"proxy": {
"server": "http://default-proxy.com:3128",
"username": "user1",
"password": "pass1",
},
},
"alternative": {
"proxy": {
"server": "http://alternative-proxy.com:3128",
"username": "user2",
"password": "pass2",
},
},
}
或者在 爬取时创建上下文 时传递 proxy
键。
另请参阅
zyte-smartproxy-playwright
:在 Playwright 的 Node.js 版本中无缝支持 Zyte Smart Proxy Manager。- 有关 HTTP 代理的 Python 版 Playwright 部分。
在页面上执行操作
将 PageMethod
对象的排序可迭代对象(例如 list
、tuple
、dict
)传递到 playwright_page_methods
Request.meta 键中,以请求要在返回最终 Response
到回调之前在 Page
对象上调用的方法。
当您需要在页面上执行某些操作(如向下滚动或点击链接)并只想在回调中处理最终结果时,这很有用。
PageMethod
类
scrapy_playwright.page.PageMethod(method: str, *args, **kwargs)
:
表示要在 playwright.page.Page
对象上调用(如果需要则等待)的方法(例如 "click"、"screenshot"、"evaluate" 等)。method
是方法名称,*args
和 **kwargs
是调用此方法时传递的。返回值将存储在 PageMethod.result
属性中。
例如
def start_requests(self):
yield Request(
url="https://example.org",
meta={
"playwright": True,
"playwright_page_methods": [
PageMethod("screenshot", path="example.png", full_page=True),
],
},
)
def parse(self, response, **kwargs):
screenshot = response.meta["playwright_page_methods"][0]
# screenshot.result contains the image's bytes
产生的效果与
def start_requests(self):
yield Request(
url="https://example.org",
meta={"playwright": True, "playwright_include_page": True},
)
async def parse(self, response, **kwargs):
page = response.meta["playwright_page"]
screenshot = await page.screenshot(path="example.png", full_page=True)
# screenshot contains the image's bytes
await page.close()
支持的方法
有关可用方法的详细信息,请参阅 Page 类的上游文档。
对响应对象的影响
某些 Response
属性(例如 url
、ip_address
)反映了在页面上执行的最后一个动作后的状态。如果您发出一个导致导航(例如在链接上单击)的 PageMethod
,则 Response.url
属性将指向新的 URL,这可能与请求的 URL 不同。
处理页面事件
可以在 playwright_page_event_handlers
Request.meta 键中指定页面事件处理器字典。键是要处理的事件名称(例如 dialog
、download
等)。值可以是可调用对象或字符串(在这种情况下,将查找具有该名称的爬虫方法)。
示例
from playwright.async_api import Dialog
async def handle_dialog(dialog: Dialog) -> None:
logging.info(f"Handled dialog with message: {dialog.message}")
await dialog.dismiss()
class EventSpider(scrapy.Spider):
name = "event"
def start_requests(self):
yield scrapy.Request(
url="https://example.org",
meta={
"playwright": True,
"playwright_page_event_handlers": {
"dialog": handle_dialog,
"response": "handle_response",
},
},
)
async def handle_response(self, response: PlaywrightResponse) -> None:
logging.info(f"Received response with URL {response.url}")
查看 上游 Page
文档 了解接受的事件及其处理器的参数列表。
关于页面事件处理器的说明
- 事件处理器将保留在页面中,并将对使用相同页面的后续下载进行调用,除非之后被删除。这通常不是问题,因为默认情况下请求是在单次使用的页面中执行的。
- 事件处理器将处理 Playwright 对象,而不是 Scrapy 对象。例如,对于每个 Scrapy 请求/响应,都存在对应的 Playwright 请求/响应,但反之则不然:获取图像、脚本、样式表等的后台请求/响应不会被 Scrapy 所见。
内存使用扩展
默认的 Scrapy 内存使用扩展(scrapy.extensions.memusage.MemoryUsage
)不包括 Playwright 所使用的内存,因为浏览器是以单独进程启动的。scrapy-playwright 包提供了一种替换扩展,该扩展也考虑了 Playwright 所使用的内存。此扩展需要 psutil
包才能工作。
更新 EXTENSIONS 设置以禁用内置的 Scrapy 扩展,并用 scrapy-playwright 包中的扩展替换
# settings.py
EXTENSIONS = {
"scrapy.extensions.memusage.MemoryUsage": None,
"scrapy_playwright.memusage.ScrapyPlaywrightMemoryUsageExtension": 0,
}
有关受支持的设置的更多信息,请参阅上游文档。
Windows支持
就像上游 Scrapy 扩展一样,此自定义内存扩展在 Windows 上不可用。这是因为 stdlib resource
模块不可用。
示例
点击链接,将结果页面保存为 PDF
class ClickAndSavePdfSpider(scrapy.Spider):
name = "pdf"
def start_requests(self):
yield scrapy.Request(
url="https://example.org",
meta=dict(
playwright=True,
playwright_page_methods={
"click": PageMethod("click", selector="a"),
"pdf": PageMethod("pdf", path="/tmp/file.pdf"),
},
),
)
def parse(self, response, **kwargs):
pdf_bytes = response.meta["playwright_page_methods"]["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"
在无限滚动页面上向下滚动,捕获全页截图
class ScrollSpider(scrapy.Spider):
name = "scroll"
def start_requests(self):
yield scrapy.Request(
url="http://quotes.toscrape.com/scroll",
meta=dict(
playwright=True,
playwright_include_page=True,
playwright_page_methods=[
PageMethod("wait_for_selector", "div.quote"),
PageMethod("evaluate", "window.scrollBy(0, document.body.scrollHeight)"),
PageMethod("wait_for_selector", "div.quote:nth-child(11)"), # 10 per page
],
),
)
async def parse(self, response, **kwargs):
page = response.meta["playwright_page"]
await page.screenshot(path="quotes.png", full_page=True)
await page.close()
return {"quote_count": len(response.css("div.quote"))} # quotes from several pages
有关更多信息,请参阅示例目录。
已知问题
不支持按请求代理
不支持通过 proxy
请求元键指定代理。有关更多信息,请参阅代理支持部分。
不支持信号
headers_received
和 bytes_received
信号不会被 scrapy-playwright 下载处理器触发。
报告问题
在打开问题之前,请确保意外行为只能通过使用此包来观察,而不能通过独立的 Playwright 来观察。为此,将您的爬虫代码翻译为接近合理的 Playwright 脚本:如果问题以这种方式发生,您应该将其报告给上游。例如
import scrapy
class ExampleSpider(scrapy.Spider):
name = "example"
def start_requests(self):
yield scrapy.Request(
url="https://example.org",
meta=dict(
playwright=True,
playwright_page_methods=[
PageMethod("screenshot", path="example.png", full_page=True),
],
),
)
大致翻译为
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as pw:
browser = await pw.chromium.launch()
page = await browser.new_page()
await page.goto("https://example.org")
await page.screenshot(path="example.png", full_page=True)
await browser.close()
asyncio.run(main())
软件版本
请确保包括您使用的 Scrapy、Playwright 和 scrapy-playwright 的版本
$ playwright --version
Version 1.44.0
$ python -c "import scrapy_playwright; print(scrapy_playwright.__version__)"
0.0.34
$ scrapy version -v
Scrapy : 2.11.1
lxml : 5.1.0.0
libxml2 : 2.12.3
cssselect : 1.2.0
parsel : 1.8.1
w3lib : 2.1.2
Twisted : 23.10.0
Python : 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]
pyOpenSSL : 24.0.0 (OpenSSL 3.2.1 30 Jan 2024)
cryptography : 42.0.5
Platform : Linux-6.5.0-35-generic-x86_64-with-glibc2.35
可复现代码示例
在打开问题之前,请包括一个最小化、可复现示例,该示例显示了报告的行为。此外,请尽可能使代码自包含,这样就不需要活动的 Scrapy 项目,并且可以执行爬虫,只需通过 scrapy runspider
从文件中直接执行。这通常意味着在爬虫的custom_settings
属性中包括相关的设置
import scrapy
class ExampleSpider(scrapy.Spider):
name = "example"
custom_settings = {
"TWISTED_REACTOR": "twisted.internet.asyncioreactor.AsyncioSelectorReactor",
"DOWNLOAD_HANDLERS": {
"https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
"http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
},
}
def start_requests(self):
yield scrapy.Request(
url="https://example.org",
meta={"playwright": True},
)
最小化代码
请尽量将代码缩减到最少,同时仍能展示问题。完整项目(包括中间件、管道、项目处理等)实际上很少是重现问题的必要条件。不包含实际调试尝试的报告将不予考虑。
日志和统计数据
显示问题的详细信息的爬虫作业日志非常有用,有助于理解可能的错误。请包括问题和问题之前之后的行,而不仅仅是孤立的跟踪信息。作业结束时显示的作业统计数据也非常重要。
常见问题解答
查看常见问题解答文档。
弃用策略
弃用的功能将在它们被弃用后的发布之日起至少支持六个月。之后,它们可能随时被删除。有关弃用和删除的更多信息,请参阅变更日志。
项目详情
下载文件
下载适用于您平台的自定义文件。如果您不确定选择哪一个,请了解更多关于安装包的信息。