zed-scrapy-playwright 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zed_scrapy_playwright-0.1.0/PKG-INFO +121 -0
- zed_scrapy_playwright-0.1.0/README.md +109 -0
- zed_scrapy_playwright-0.1.0/pyproject.toml +22 -0
- zed_scrapy_playwright-0.1.0/pyproject.toml.orig +21 -0
- zed_scrapy_playwright-0.1.0/src/zed_scrapy_playwright/__init__.py +1 -0
- zed_scrapy_playwright-0.1.0/src/zed_scrapy_playwright/definition.py +104 -0
- zed_scrapy_playwright-0.1.0/src/zed_scrapy_playwright/example.py +25 -0
- zed_scrapy_playwright-0.1.0/src/zed_scrapy_playwright/handler.py +115 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: zed-scrapy-playwright
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author: zhouzhouos
|
|
6
|
+
Author-email: zhouzhouos <leisure-zyf@qq.com>
|
|
7
|
+
Requires-Dist: playwright>=1.62.0
|
|
8
|
+
Requires-Dist: playwright-stealth>=2.0.3
|
|
9
|
+
Requires-Dist: scrapy>=2.18.0
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# zed-scrapy-playwright
|
|
14
|
+
|
|
15
|
+
> Less meta config, more Python code.
|
|
16
|
+
|
|
17
|
+
> 把 Scrapy 变成 Playwright 的事件调度引擎。
|
|
18
|
+
|
|
19
|
+
[English](docs/README-en.md)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 简介
|
|
24
|
+
|
|
25
|
+
`zed-scrapy-playwright` 是一个 Scrapy 中间件,它重新定义了 Scrapy 与 Playwright 的集成方式——**让 Scrapy 成为事件调度器,让 Playwright 成为可编程的执行引擎。**
|
|
26
|
+
|
|
27
|
+
不同于传统方案将每个请求限制为单次页面导航,本中间件允许你在一个 `execution` 中完成完整的业务流程:登录、搜索、详情、下单,一气呵成。
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 核心特性
|
|
32
|
+
|
|
33
|
+
- **会话级控制** — 单次 execution 内多次 `goto`,登录态自动保持
|
|
34
|
+
- **线性编码** — 复杂流程写在一个函数里,告别 callback 地狱
|
|
35
|
+
- **精准数据传输** — 在 Page 中直接提取初筛数据,避免传输冗余 HTML
|
|
36
|
+
- **完全开放** — 任意 Playwright API 均可使用,无预定义操作限制
|
|
37
|
+
- **双向交叉检查** — 中间件与爬虫相互验证,配置错误快速失败
|
|
38
|
+
- **Stealth 灵活可控** — 可全局注入,也可在每个 execution 中自定义
|
|
39
|
+
- **类型安全** — 完整的类型提示,IDE 友好
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 快速开始
|
|
44
|
+
|
|
45
|
+
### 安装
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install zed-scrapy-playwright
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 配置 settings.py
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
DOWNLOADER_MIDDLEWARES = {
|
|
55
|
+
"zed_scrapy_playwright.handler.PlaywrightDownloaderMiddleware": 300,
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 编写爬虫
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import zed_scrapy_playwright as zsp
|
|
63
|
+
from playwright.async_api import Page
|
|
64
|
+
|
|
65
|
+
class MySpider(zsp.Spider):
|
|
66
|
+
name = "example"
|
|
67
|
+
|
|
68
|
+
async def start(self):
|
|
69
|
+
yield zsp.Request(
|
|
70
|
+
exec_type="NewPage",
|
|
71
|
+
execution=self.search_and_scrape,
|
|
72
|
+
callback=self.parse,
|
|
73
|
+
meta={"keyword": "laptop"},
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
async def search_and_scrape(request: zsp.Request, page: Page):
|
|
78
|
+
keyword = request.meta["keyword"]
|
|
79
|
+
|
|
80
|
+
await page.goto("https://example.com")
|
|
81
|
+
await page.fill("#search", keyword)
|
|
82
|
+
await page.click("#search-btn")
|
|
83
|
+
await page.wait_for_selector(".results")
|
|
84
|
+
|
|
85
|
+
# 初筛数据:直接在页面中提取
|
|
86
|
+
items = await page.evaluate("""
|
|
87
|
+
Array.from(document.querySelectorAll('.item')).map(el => ({
|
|
88
|
+
title: el.querySelector('.title')?.innerText,
|
|
89
|
+
price: el.querySelector('.price')?.innerText,
|
|
90
|
+
}))
|
|
91
|
+
""")
|
|
92
|
+
|
|
93
|
+
return {"items": items, "keyword": keyword}
|
|
94
|
+
|
|
95
|
+
async def parse(self, response: zsp.Response):
|
|
96
|
+
data = response.data # 初筛数据
|
|
97
|
+
for item in data["items"]:
|
|
98
|
+
yield {
|
|
99
|
+
"title": item["title"],
|
|
100
|
+
"price": float(item["price"].replace("$", "")),
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 对比 scrapy-playwright
|
|
107
|
+
|
|
108
|
+
| 维度 | scrapy-playwright | zed-scrapy-playwright |
|
|
109
|
+
|------|:---:|:---:|
|
|
110
|
+
| 操作自由度 | 有限 | 无限 |
|
|
111
|
+
| 多次 goto | ❌ | ✅ |
|
|
112
|
+
| 登录态保持 | 配置复杂 | 天然支持 |
|
|
113
|
+
| 代码组织 | 分散 callback | 线性聚合 |
|
|
114
|
+
| 数据传输 | 完整 HTML | 初筛数据 |
|
|
115
|
+
| 防误用机制 | 无 | 双向交叉检查 |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 许可证
|
|
120
|
+
|
|
121
|
+
暂定:MIT
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# zed-scrapy-playwright
|
|
2
|
+
|
|
3
|
+
> Less meta config, more Python code.
|
|
4
|
+
|
|
5
|
+
> 把 Scrapy 变成 Playwright 的事件调度引擎。
|
|
6
|
+
|
|
7
|
+
[English](docs/README-en.md)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 简介
|
|
12
|
+
|
|
13
|
+
`zed-scrapy-playwright` 是一个 Scrapy 中间件,它重新定义了 Scrapy 与 Playwright 的集成方式——**让 Scrapy 成为事件调度器,让 Playwright 成为可编程的执行引擎。**
|
|
14
|
+
|
|
15
|
+
不同于传统方案将每个请求限制为单次页面导航,本中间件允许你在一个 `execution` 中完成完整的业务流程:登录、搜索、详情、下单,一气呵成。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 核心特性
|
|
20
|
+
|
|
21
|
+
- **会话级控制** — 单次 execution 内多次 `goto`,登录态自动保持
|
|
22
|
+
- **线性编码** — 复杂流程写在一个函数里,告别 callback 地狱
|
|
23
|
+
- **精准数据传输** — 在 Page 中直接提取初筛数据,避免传输冗余 HTML
|
|
24
|
+
- **完全开放** — 任意 Playwright API 均可使用,无预定义操作限制
|
|
25
|
+
- **双向交叉检查** — 中间件与爬虫相互验证,配置错误快速失败
|
|
26
|
+
- **Stealth 灵活可控** — 可全局注入,也可在每个 execution 中自定义
|
|
27
|
+
- **类型安全** — 完整的类型提示,IDE 友好
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 快速开始
|
|
32
|
+
|
|
33
|
+
### 安装
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install zed-scrapy-playwright
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 配置 settings.py
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
DOWNLOADER_MIDDLEWARES = {
|
|
43
|
+
"zed_scrapy_playwright.handler.PlaywrightDownloaderMiddleware": 300,
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 编写爬虫
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import zed_scrapy_playwright as zsp
|
|
51
|
+
from playwright.async_api import Page
|
|
52
|
+
|
|
53
|
+
class MySpider(zsp.Spider):
|
|
54
|
+
name = "example"
|
|
55
|
+
|
|
56
|
+
async def start(self):
|
|
57
|
+
yield zsp.Request(
|
|
58
|
+
exec_type="NewPage",
|
|
59
|
+
execution=self.search_and_scrape,
|
|
60
|
+
callback=self.parse,
|
|
61
|
+
meta={"keyword": "laptop"},
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
async def search_and_scrape(request: zsp.Request, page: Page):
|
|
66
|
+
keyword = request.meta["keyword"]
|
|
67
|
+
|
|
68
|
+
await page.goto("https://example.com")
|
|
69
|
+
await page.fill("#search", keyword)
|
|
70
|
+
await page.click("#search-btn")
|
|
71
|
+
await page.wait_for_selector(".results")
|
|
72
|
+
|
|
73
|
+
# 初筛数据:直接在页面中提取
|
|
74
|
+
items = await page.evaluate("""
|
|
75
|
+
Array.from(document.querySelectorAll('.item')).map(el => ({
|
|
76
|
+
title: el.querySelector('.title')?.innerText,
|
|
77
|
+
price: el.querySelector('.price')?.innerText,
|
|
78
|
+
}))
|
|
79
|
+
""")
|
|
80
|
+
|
|
81
|
+
return {"items": items, "keyword": keyword}
|
|
82
|
+
|
|
83
|
+
async def parse(self, response: zsp.Response):
|
|
84
|
+
data = response.data # 初筛数据
|
|
85
|
+
for item in data["items"]:
|
|
86
|
+
yield {
|
|
87
|
+
"title": item["title"],
|
|
88
|
+
"price": float(item["price"].replace("$", "")),
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 对比 scrapy-playwright
|
|
95
|
+
|
|
96
|
+
| 维度 | scrapy-playwright | zed-scrapy-playwright |
|
|
97
|
+
|------|:---:|:---:|
|
|
98
|
+
| 操作自由度 | 有限 | 无限 |
|
|
99
|
+
| 多次 goto | ❌ | ✅ |
|
|
100
|
+
| 登录态保持 | 配置复杂 | 天然支持 |
|
|
101
|
+
| 代码组织 | 分散 callback | 线性聚合 |
|
|
102
|
+
| 数据传输 | 完整 HTML | 初筛数据 |
|
|
103
|
+
| 防误用机制 | 无 | 双向交叉检查 |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 许可证
|
|
108
|
+
|
|
109
|
+
暂定:MIT
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "zed-scrapy-playwright"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"playwright>=1.62.0",
|
|
9
|
+
"playwright-stealth>=2.0.3",
|
|
10
|
+
"scrapy>=2.18.0",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[[project.authors]]
|
|
14
|
+
name = "zhouzhouos"
|
|
15
|
+
email = "leisure-zyf@qq.com"
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
zed-scrapy-playwright = "zed_scrapy_playwright:main"
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["uv_build>=0.12.5,<0.13.0"]
|
|
22
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "zed-scrapy-playwright"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "zhouzhouos", email = "leisure-zyf@qq.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"playwright>=1.62.0",
|
|
12
|
+
"playwright-stealth>=2.0.3",
|
|
13
|
+
"scrapy>=2.18.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.scripts]
|
|
17
|
+
zed-scrapy-playwright = "zed_scrapy_playwright:main"
|
|
18
|
+
|
|
19
|
+
[build-system]
|
|
20
|
+
requires = ["uv_build>=0.12.5,<0.13.0"]
|
|
21
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .definition import PREFIX, Request, Response, Spider
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# from scrapy.http import Response, TextResponse
|
|
2
|
+
from collections.abc import Awaitable, Callable
|
|
3
|
+
from typing import Any, Literal, Self
|
|
4
|
+
|
|
5
|
+
import scrapy
|
|
6
|
+
import scrapy.http
|
|
7
|
+
from playwright.async_api import Page
|
|
8
|
+
from scrapy.crawler import Crawler
|
|
9
|
+
from scrapy.exceptions import NotConfigured
|
|
10
|
+
|
|
11
|
+
PREFIX = "playwright"
|
|
12
|
+
ExecType = Literal["NewPage"]
|
|
13
|
+
|
|
14
|
+
X = "zed_scrapy_playwright.handler.PlaywrightDownloaderMiddleware"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Response(scrapy.http.Response):
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
result,
|
|
21
|
+
url=f"{PREFIX}://response",
|
|
22
|
+
status=200,
|
|
23
|
+
headers=None,
|
|
24
|
+
body=b"",
|
|
25
|
+
flags=None,
|
|
26
|
+
request=None,
|
|
27
|
+
certificate=None,
|
|
28
|
+
ip_address=None,
|
|
29
|
+
protocol=None,
|
|
30
|
+
):
|
|
31
|
+
super().__init__(
|
|
32
|
+
url,
|
|
33
|
+
status,
|
|
34
|
+
headers,
|
|
35
|
+
body,
|
|
36
|
+
flags,
|
|
37
|
+
request,
|
|
38
|
+
certificate,
|
|
39
|
+
ip_address,
|
|
40
|
+
protocol,
|
|
41
|
+
)
|
|
42
|
+
self.result: Any = result
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Request(scrapy.Request):
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
exec_type: ExecType = "NewPage",
|
|
49
|
+
execution: Callable[["Request", Page], Awaitable[Any]] | None = None,
|
|
50
|
+
callback=None,
|
|
51
|
+
# method="GET",
|
|
52
|
+
# headers=None,
|
|
53
|
+
# body=None,
|
|
54
|
+
# cookies=None,
|
|
55
|
+
meta=None,
|
|
56
|
+
# encoding="utf-8",
|
|
57
|
+
# priority=0,
|
|
58
|
+
dont_filter=True,
|
|
59
|
+
# errback=None,
|
|
60
|
+
# flags=None,
|
|
61
|
+
# cb_kwargs=None,
|
|
62
|
+
):
|
|
63
|
+
super().__init__(
|
|
64
|
+
url=f"{PREFIX}://{exec_type}",
|
|
65
|
+
callback=callback,
|
|
66
|
+
# method,
|
|
67
|
+
# headers,
|
|
68
|
+
# body,
|
|
69
|
+
# cookies,
|
|
70
|
+
meta=meta,
|
|
71
|
+
# encoding,
|
|
72
|
+
# priority,
|
|
73
|
+
# dont_filter,
|
|
74
|
+
# errback,
|
|
75
|
+
# flags,
|
|
76
|
+
# cb_kwargs,
|
|
77
|
+
)
|
|
78
|
+
self.exec_type: ExecType = exec_type
|
|
79
|
+
self.execution = execution if execution else self._default_execution
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
async def _default_execution(request: "Request", page: Page):
|
|
83
|
+
print("Warning")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Spider(scrapy.Spider):
|
|
87
|
+
"""用以判断是否启用此对应中间件的环境"""
|
|
88
|
+
|
|
89
|
+
info = {"headless": False, "executable_path": None}
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_crawler(cls, crawler: Crawler, *args: Any, **kwargs: Any) -> Self:
|
|
93
|
+
spider = cls(*args, **kwargs)
|
|
94
|
+
spider._set_crawler(crawler)
|
|
95
|
+
|
|
96
|
+
if X not in spider.settings.getdict("DOWNLOADER_MIDDLEWARES"):
|
|
97
|
+
raise NotConfigured(
|
|
98
|
+
f"没有注册 {X}, 则不能启用该中间件及其对应的爬虫类 {type(spider)}。"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return spider
|
|
102
|
+
|
|
103
|
+
def __init__(self, name: str | None = None, **kwargs: dict):
|
|
104
|
+
super().__init__(name, **kwargs)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from playwright.async_api import Page
|
|
2
|
+
|
|
3
|
+
import zed_scrapy_playwright as zsp
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ExampleExecutor(zsp.Spider):
|
|
7
|
+
name = "example"
|
|
8
|
+
info = {"headless": False, "executable_path": "/opt/google/chrome/chrome"}
|
|
9
|
+
# allowed_domains = ["example.com"]
|
|
10
|
+
# start_urls = ["https://example.com"]
|
|
11
|
+
|
|
12
|
+
async def start(self):
|
|
13
|
+
|
|
14
|
+
yield zsp.Request(
|
|
15
|
+
execution=self.exection,
|
|
16
|
+
callback=self.parse,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
async def exection(request: zsp.Request, page: Page):
|
|
21
|
+
await page.goto("https://example.com")
|
|
22
|
+
return await page.content()
|
|
23
|
+
|
|
24
|
+
def parse(self, response: zsp.Response):
|
|
25
|
+
print(response.result)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Define here the models for your spider middleware
|
|
2
|
+
#
|
|
3
|
+
# See documentation in:
|
|
4
|
+
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html
|
|
5
|
+
|
|
6
|
+
# useful for handling different item types with a single interface
|
|
7
|
+
# from itemadapter import ItemAdapter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from playwright.async_api import async_playwright
|
|
11
|
+
from playwright_stealth import Stealth
|
|
12
|
+
from scrapy import Request, crawler, signals
|
|
13
|
+
|
|
14
|
+
from . import definition as Zed
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PlaywrightDownloaderMiddleware:
|
|
18
|
+
# Not all methods need to be defined. If a method is not defined,
|
|
19
|
+
# scrapy acts as if the downloader middleware does not modify the
|
|
20
|
+
# passed objects.
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_crawler(cls, crawler: crawler.Crawler):
|
|
24
|
+
# This method is used by Scrapy to create your spiders.
|
|
25
|
+
s = cls()
|
|
26
|
+
s.c = crawler
|
|
27
|
+
# 连接中间件在爬虫开始时的触发事件
|
|
28
|
+
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
|
|
29
|
+
# 连接中间件在爬虫结束时的触发事件
|
|
30
|
+
crawler.signals.connect(s.spider_closed, signal=signals.spider_closed)
|
|
31
|
+
return s
|
|
32
|
+
|
|
33
|
+
async def process_request(self, request: Request, spider=None):
|
|
34
|
+
# Called for each request that goes through the downloader
|
|
35
|
+
# middleware.
|
|
36
|
+
|
|
37
|
+
# Must either:
|
|
38
|
+
# - return None: continue processing this request
|
|
39
|
+
# - or return a Response object
|
|
40
|
+
# - or return a Request object
|
|
41
|
+
# - or raise IgnoreRequest: process_exception() methods of
|
|
42
|
+
# installed downloader middleware will be called
|
|
43
|
+
|
|
44
|
+
# 这里处理主动发起的自定义的 request 类型
|
|
45
|
+
if isinstance(request, Zed.Request):
|
|
46
|
+
print("主动发起", request.url, request.meta)
|
|
47
|
+
|
|
48
|
+
match request.exec_type:
|
|
49
|
+
case "NewPage":
|
|
50
|
+
page = await self.context.new_page()
|
|
51
|
+
|
|
52
|
+
if request.meta.get("no-stealth"):
|
|
53
|
+
pass
|
|
54
|
+
else:
|
|
55
|
+
print("已为 new page 自动施加 Stealth.apply_stealth_async")
|
|
56
|
+
await Stealth().apply_stealth_async(page)
|
|
57
|
+
|
|
58
|
+
r = await request.execution(request, page)
|
|
59
|
+
return Zed.Response(r, request=request)
|
|
60
|
+
|
|
61
|
+
case _:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
# 这里处理自动发起的 scrapy.Request 类型,比如 <class 'scrapy.http.request.Request'> wpwp://nothing/robots.txt
|
|
65
|
+
if request.url.startswith(Zed.PREFIX):
|
|
66
|
+
print("自动请求", request.url)
|
|
67
|
+
return Zed.Response("", request=request)
|
|
68
|
+
|
|
69
|
+
# 其他的寻常的 request 不在这里截留,让其 continue
|
|
70
|
+
|
|
71
|
+
def process_response(self, request, response, spider=None):
|
|
72
|
+
# Called with the response returned from the downloader.
|
|
73
|
+
|
|
74
|
+
# Must either;
|
|
75
|
+
# - return a Response object
|
|
76
|
+
# - return a Request object
|
|
77
|
+
# - or raise IgnoreRequest
|
|
78
|
+
return response
|
|
79
|
+
|
|
80
|
+
def process_exception(self, request, exception, spider=None):
|
|
81
|
+
# Called when a download handler or a process_request()
|
|
82
|
+
# (from other downloader middleware) raises an exception.
|
|
83
|
+
|
|
84
|
+
# Must either:
|
|
85
|
+
# - return None: continue processing this exception
|
|
86
|
+
# - return a Response object: stops process_exception() chain
|
|
87
|
+
# - return a Request object: stops process_exception() chain
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
async def spider_opened(self, spider=None):
|
|
91
|
+
self.c: crawler.Crawler
|
|
92
|
+
if not isinstance(self.c.spider, Zed.Spider):
|
|
93
|
+
print(
|
|
94
|
+
"Warning:",
|
|
95
|
+
f"该爬虫类没有继承于 {type(self)},将不会进行此中间件环境的启用",
|
|
96
|
+
)
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
print("spider_opened, start the initialization of async playwright api object.")
|
|
100
|
+
self.pm = async_playwright()
|
|
101
|
+
self.pr = await self.pm.start()
|
|
102
|
+
self.browser = await self.pr.chromium.launch(**self.c.spider.info)
|
|
103
|
+
self.context = await self.browser.new_context(no_viewport=True)
|
|
104
|
+
|
|
105
|
+
async def spider_closed(self):
|
|
106
|
+
if not isinstance(self.c.spider, Zed.Spider):
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
print(
|
|
110
|
+
"spider_closed, start the finalizing work of async playwright api object."
|
|
111
|
+
)
|
|
112
|
+
await self.context.close()
|
|
113
|
+
await self.browser.close()
|
|
114
|
+
await self.pr.stop()
|
|
115
|
+
await self.pm.__aexit__()
|