300 lines
9.7 KiB
Markdown
300 lines
9.7 KiB
Markdown
# 自定义下载器
|
||
|
||
下载器一共分为三种:**普通下载器**、**支持保持session的下载器**以及**浏览器渲染下载器**。默认已经在框架中内置,setting中的配置如下
|
||
|
||
```
|
||
DOWNLOADER = "feapder.network.downloader.RequestsDownloader" # 请求下载器
|
||
SESSION_DOWNLOADER = "feapder.network.downloader.RequestsSessionDownloader"
|
||
RENDER_DOWNLOADER = "feapder.network.downloader.SeleniumDownloader" # 渲染下载器
|
||
```
|
||
|
||
- session下载器当配置中`USE_SESSION = True`时会启用
|
||
- 渲染下载器当使用浏览器下载功能时会启用
|
||
|
||
这些下载器均为插件的形式,我们可以自定义
|
||
|
||
## 自定义普通下载器
|
||
|
||
1. 编写下载器。如在 `xxx-spider/downloader/my_downloader.py `下自定义了如下下载器
|
||
|
||
```
|
||
import requests
|
||
|
||
from feapder.network.downloader.base import Downloader
|
||
from feapder.network.response import Response
|
||
|
||
class RequestsDownloader(Downloader):
|
||
def download(self, request) -> Response:
|
||
response = requests.request(
|
||
request.method, request.url, **request.requests_kwargs
|
||
)
|
||
# 将requests的response转化为feapder的Response 对象,方便后续解析时使用xpath、re等方法
|
||
response = Response(response)
|
||
return response
|
||
```
|
||
|
||
注:这里返回的response对象不强制要求为是feapder的Response。返回值会传到解析函数的response参数里,若返回的是文本,则接收到的也是文本。
|
||
|
||
但为了代码可读性,建议将返回值转为feapder的Response后再返回。
|
||
|
||
转feapder的Response的方式有如下几种
|
||
|
||
```
|
||
# 方式1
|
||
# response参数为reqeusts的response
|
||
Response(response)
|
||
|
||
# 方式2
|
||
Response.from_text(text="html内容")
|
||
```
|
||
|
||
2. 在settings中指定下载器
|
||
|
||
```
|
||
DOWNLOADER = "downloader.my_downloader.RequestsDownloader"
|
||
```
|
||
|
||
## 自定义session下载器
|
||
|
||
1. 和普通下载器一样,都是继承`Downloader`,如何保持session,可自定义。代码示例 `xxx-spider/downloader/my_downloader.py `
|
||
|
||
```
|
||
class RequestsSessionDownloader(Downloader):
|
||
session = None
|
||
|
||
@property
|
||
def _session(self):
|
||
if not self.__class__.session:
|
||
self.__class__.session = requests.Session()
|
||
# pool_connections – 缓存的 urllib3 连接池个数 pool_maxsize – 连接池中保存的最大连接数
|
||
http_adapter = HTTPAdapter(pool_connections=1000, pool_maxsize=1000)
|
||
# 任何使用该session会话的 HTTP 请求,只要其 URL 是以给定的前缀开头,该传输适配器就会被使用到。
|
||
self.__class__.session.mount("http", http_adapter)
|
||
|
||
return self.__class__.session
|
||
|
||
def download(self, request) -> Response:
|
||
response = self._session.request(
|
||
request.method, request.url, **request.requests_kwargs
|
||
)
|
||
response = Response(response)
|
||
return response
|
||
```
|
||
|
||
2. 在settings中指定下载器
|
||
|
||
```
|
||
SESSION_DOWNLOADER = "downloader.my_downloader.RequestsSessionDownloader"
|
||
```
|
||
|
||
注意,这里要配置 `SESSION_DOWNLOADER`
|
||
|
||
## 自定义浏览器渲染下载器
|
||
|
||
1. 编写下载器 `xxx-spider/downloader/my_downloader.py `
|
||
|
||
**若浏览器框架本身不支持多线程,但想在多线程中使用,如playwright使用,参考如下:**
|
||
|
||
```
|
||
import feapder.setting as setting
|
||
import feapder.utils.tools as tools
|
||
from feapder.network.downloader.base import RenderDownloader
|
||
from feapder.network.response import Response
|
||
from feapder.utils.webdriver import WebDriverPool, PlaywrightDriver
|
||
|
||
|
||
class MyDownloader(RenderDownloader):
|
||
webdriver_pool: WebDriverPool = None
|
||
|
||
@property
|
||
def _webdriver_pool(self):
|
||
if not self.__class__.webdriver_pool:
|
||
self.__class__.webdriver_pool = WebDriverPool(
|
||
**setting.PLAYWRIGHT, driver_cls=PlaywrightDriver, thread_safe=True
|
||
)
|
||
|
||
return self.__class__.webdriver_pool
|
||
|
||
def download(self, request) -> Response:
|
||
# 代理优先级 自定义 > 配置文件 > 随机
|
||
if request.custom_proxies:
|
||
proxy = request.get_proxy()
|
||
elif setting.PLAYWRIGHT.get("proxy"):
|
||
proxy = setting.PLAYWRIGHT.get("proxy")
|
||
else:
|
||
proxy = request.get_proxy()
|
||
|
||
# user_agent优先级 自定义 > 配置文件 > 随机
|
||
if request.custom_ua:
|
||
user_agent = request.get_user_agent()
|
||
elif setting.PLAYWRIGHT.get("user_agent"):
|
||
user_agent = setting.PLAYWRIGHT.get("user_agent")
|
||
else:
|
||
user_agent = request.get_user_agent()
|
||
|
||
cookies = request.get_cookies()
|
||
url = request.url
|
||
render_time = request.render_time or setting.PLAYWRIGHT.get("render_time")
|
||
wait_until = setting.PLAYWRIGHT.get("wait_until") or "domcontentloaded"
|
||
if request.get_params():
|
||
url = tools.joint_url(url, request.get_params())
|
||
|
||
driver: PlaywrightDriver = self._webdriver_pool.get(
|
||
user_agent=user_agent, proxy=proxy
|
||
)
|
||
try:
|
||
if cookies:
|
||
driver.url = url
|
||
driver.cookies = cookies
|
||
driver.page.goto(url, wait_until=wait_until)
|
||
|
||
if render_time:
|
||
tools.delay_time(render_time)
|
||
|
||
html = driver.page.content()
|
||
response = Response.from_dict(
|
||
{
|
||
"url": driver.page.url,
|
||
"cookies": driver.cookies,
|
||
"_content": html.encode(),
|
||
"status_code": 200,
|
||
"elapsed": 666,
|
||
"headers": {
|
||
"User-Agent": driver.user_agent,
|
||
"Cookie": tools.cookies2str(driver.cookies),
|
||
},
|
||
}
|
||
)
|
||
|
||
response.driver = driver
|
||
response.browser = driver
|
||
return response
|
||
except Exception as e:
|
||
self._webdriver_pool.remove(driver)
|
||
raise e
|
||
|
||
def close(self, driver):
|
||
if driver:
|
||
self._webdriver_pool.remove(driver)
|
||
|
||
def put_back(self, driver):
|
||
"""
|
||
释放浏览器对象
|
||
"""
|
||
self._webdriver_pool.put(driver)
|
||
|
||
def close_all(self):
|
||
"""
|
||
关闭所有浏览器
|
||
"""
|
||
# 不支持
|
||
# self._webdriver_pool.close()
|
||
pass
|
||
```
|
||
|
||
这里使用了WebDriverPool,参数`thread_safe=True`,即要保证使用时的线程安全,确保同个浏览器对象只能被同一个线程调用
|
||
|
||
**若浏览器框架本身支持多线程,如selenium,则参考如下**
|
||
|
||
```
|
||
import feapder.setting as setting
|
||
import feapder.utils.tools as tools
|
||
from feapder.network.downloader.base import RenderDownloader
|
||
from feapder.network.response import Response
|
||
from feapder.utils.webdriver import WebDriverPool, SeleniumDriver
|
||
|
||
|
||
class MyDownloader(RenderDownloader):
|
||
webdriver_pool: WebDriverPool = None
|
||
|
||
@property
|
||
def _webdriver_pool(self):
|
||
if not self.__class__.webdriver_pool:
|
||
self.__class__.webdriver_pool = WebDriverPool(
|
||
**setting.WEBDRIVER, driver=SeleniumDriver
|
||
)
|
||
|
||
return self.__class__.webdriver_pool
|
||
|
||
def download(self, request) -> Response:
|
||
# 代理优先级 自定义 > 配置文件 > 随机
|
||
if request.custom_proxies:
|
||
proxy = request.get_proxy()
|
||
elif setting.WEBDRIVER.get("proxy"):
|
||
proxy = setting.WEBDRIVER.get("proxy")
|
||
else:
|
||
proxy = request.get_proxy()
|
||
|
||
# user_agent优先级 自定义 > 配置文件 > 随机
|
||
if request.custom_ua:
|
||
user_agent = request.get_user_agent()
|
||
elif setting.WEBDRIVER.get("user_agent"):
|
||
user_agent = setting.WEBDRIVER.get("user_agent")
|
||
else:
|
||
user_agent = request.get_user_agent()
|
||
|
||
cookies = request.get_cookies()
|
||
url = request.url
|
||
render_time = request.render_time or setting.WEBDRIVER.get("render_time")
|
||
if request.get_params():
|
||
url = tools.joint_url(url, request.get_params())
|
||
|
||
browser: SeleniumDriver = self._webdriver_pool.get(
|
||
user_agent=user_agent, proxy=proxy
|
||
)
|
||
try:
|
||
browser.get(url)
|
||
if cookies:
|
||
browser.cookies = cookies
|
||
# 刷新使cookie生效
|
||
browser.get(url)
|
||
|
||
if render_time:
|
||
tools.delay_time(render_time)
|
||
|
||
html = browser.page_source
|
||
response = Response.from_dict(
|
||
{
|
||
"url": browser.current_url,
|
||
"cookies": browser.cookies,
|
||
"_content": html.encode(),
|
||
"status_code": 200,
|
||
"elapsed": 666,
|
||
"headers": {
|
||
"User-Agent": browser.user_agent,
|
||
"Cookie": tools.cookies2str(browser.cookies),
|
||
},
|
||
}
|
||
)
|
||
|
||
response.driver = browser
|
||
response.browser = browser
|
||
return response
|
||
except Exception as e:
|
||
self._webdriver_pool.remove(browser)
|
||
raise e
|
||
|
||
def close(self, driver):
|
||
if driver:
|
||
self._webdriver_pool.remove(driver)
|
||
|
||
def put_back(self, driver):
|
||
"""
|
||
释放浏览器对象
|
||
"""
|
||
self._webdriver_pool.put(driver)
|
||
|
||
def close_all(self):
|
||
"""
|
||
关闭所有浏览器
|
||
"""
|
||
self._webdriver_pool.close()
|
||
```
|
||
|
||
2. 在settings中指定下载器
|
||
|
||
```
|
||
RENDER_DOWNLOADER = "downloader.my_downloader.MyDownloader"
|
||
```
|
||
|
||
注,这里要写`RENDER_DOWNLOADER` |