选择动态加载的内容
有些网页在浏览器中打开时会显示你需要的数据,但用 Scrapy 下载后,却无法通过选择器取得这些数据。 (selectors)
遇到这种情况,建议先找到数据源,再从数据源提取数据。 (find the data source)
如果无法做到这一点,但仍能通过浏览器中的 DOM 访问所需数据,请参阅“使用无头浏览器”。 (Using a headless browser)
查找数据源
要提取需要的数据,首先必须找到它的来源位置。
如果数据采用图片或 PDF 文档等非文本格式,请使用浏览器的网络工具找到对应请求,再复现该请求。 (network tool) (reproduce it)
如果浏览器允许将所需数据选为文本,这些数据可能定义在嵌入的 JavaScript 代码中,也可能以文本格式从外部资源加载。
这种情况下,可以使用 wgrep 等工具寻找该资源的 URL。
如果数据实际上来自原始 URL 本身,就需要检查网页源代码,确定数据所在的位置。 (inspect the source code of the webpage)
如果数据来自另一个 URL,则需要复现对应的请求。 (reproduce the
corresponding request)
检查网页源代码
有时需要检查网页的源代码,而不是 DOM,才能确定所需数据的位置。
使用 Scrapy 的 fetch 命令下载 Scrapy 实际看到的网页内容:
scrapy fetch --nolog https://example.com > response.html
如果所需数据位于 <script/> 元素内嵌的 JavaScript 代码中,请参阅“解析 JavaScript 代码”。 (Parsing JavaScript code)
如果找不到所需数据,先确认问题是否只出现在 Scrapy:用 curl 或 wget 之类的 HTTP 客户端下载网页,查看它们收到的响应中能否找到这些信息。
如果其他客户端收到了包含所需数据的响应,请修改 Scrapy 的 Request,使其与该客户端的请求一致。例如,尝试相同的用户代理字符串(USER_AGENT)或相同的请求头。 (headers)
如果其他客户端的响应也没有所需数据,就需要进一步使请求接近浏览器实际发出的请求。请参阅“复现请求”。 (Reproducing requests)
复现请求
有时,我们需要按照浏览器的实际行为复现请求。
使用浏览器的网络工具查看所需请求是怎样发出的,再尝试用 Scrapy 复现。 (network tool)
有时,只需 yield 一个 HTTP 方法和 URL 相同的 Request 就够了;但也可能必须复现请求体、请求头和表单参数。表单参数的细节见“创建提交 HTML 表单的请求”。 (Creating requests that submit HTML forms)
主流浏览器都可以将请求导出为 curl 格式,因此 Scrapy 提供了 from_curl() 方法,可以从 cURL 命令生成等价的 Request。更多信息请参阅网络工具章节中的“从 curl 创建请求”。 (request from curl)
获得预期响应后,就可以从中提取需要的数据。 (extract the desired data from
it)
Scrapy 能够复现任何请求。不过,复现全部必要请求有时会消耗过多开发时间。如果你遇到这种情况,而且抓取速度并非主要考虑因素,也可以改用无头浏览器。 (using a headless browser)
如果有时能得到预期响应、有时却不能,问题很可能不在请求本身,而在目标服务器。目标服务器可能存在缺陷、负载过高,或者封禁了部分请求。 (banning)
另外,可以使用 curl2scrapy 将 cURL 命令转换为 Scrapy 请求。
处理不同的响应格式
拿到包含所需数据的响应后,提取方式取决于响应的类型:
-
如果响应为 HTML、XML 或 JSON,就像平常一样使用选择器。 (selectors)
-
如果响应为 JSON,使用 response.json() 加载所需数据:
data = response.json()如果所需数据位于 JSON 数据中嵌入的 HTML 或 XML 代码里,可以把这些 HTML 或 XML 代码加载到 Selector 中,然后照常使用: (use it)
selector = Selector(text=data["html"]) -
如果响应为 JavaScript,或者 HTML 中的
<script/>元素包含所需数据,请参阅“解析 JavaScript 代码”。 (Parsing JavaScript code) -
如果响应为 CSS,使用正则表达式从 response.text 中提取所需数据。 (regular expression)
-
如果响应为图片或其他基于图片的格式,例如 PDF,可以从 response.body 读取响应字节,再用 OCR 方案将所需数据提取为文本。
例如,可以使用 pytesseract。若要从 PDF 读取表格,tabula-py 可能更合适。
-
如果响应为 SVG,或者 HTML 中嵌入的 SVG 包含所需数据,因为 SVG 基于 XML,可能可以用选择器直接提取。 (selectors)
否则,可能需要将 SVG 代码转换为位图,再处理位图。 (handle that raster image)
解析 JavaScript 代码
如果所需数据被硬编码在 JavaScript 中,首先需要取得 JavaScript 代码:
-
如果代码位于 JavaScript 文件中,直接读取 response.text。
-
如果代码位于 HTML 页面的
<script/>元素中,使用选择器提取该<script/>元素内的文本。 (selectors)
取得包含 JavaScript 代码的字符串后,可以从中提取所需数据:
-
可以尝试用正则表达式提取 JSON 格式的数据,再用 json.loads() 解析。 (regular expression)
例如,如果 JavaScript 代码中单独一行写着
var data = {"field": "value"};,可以按以下方式提取:>>> pattern = r"\bvar\s+data\s*=\s*(\{.*?\})\s*;\s*\n" >>> json_data = response.css("script::text").re_first(pattern) >>> json.loads(json_data) {'field': 'value'} -
chompjs 提供了将 JavaScript 对象解析为 dict 的 API。
例如,如果 JavaScript 代码包含
var data = {field: "value", secondField: "second value"};,可以按以下方式提取:>>> import chompjs >>> javascript = response.css("script::text").get() >>> data = chompjs.parse_js_object(javascript) >>> data {'field': 'value', 'secondField': 'second value'} -
其他情况下,可以使用 js2xml 将 JavaScript 代码转换为 XML 文档,再使用选择器解析。 (selectors)
例如,如果 JavaScript 代码包含
var data = {field: "value"};,可以按以下方式提取:>>> import js2xml >>> import lxml.etree >>> from parsel import Selector >>> javascript = response.css("script::text").get() >>> xml = lxml.etree.tostring(js2xml.parse(javascript), encoding="unicode") >>> selector = Selector(text=xml) >>> selector.css('var[name="data"]').get() '<var name="data"><object><property name="field"><string>value</string></property></object></var>'
使用无头浏览器
对于通过额外请求获取数据的网页,首选方案是复现那些包含所需数据的请求。这通常值得投入:可以用尽可能少的解析时间和网络传输获得结构化、完整的数据。
不过,有些请求确实很难复现;或者你需要的东西无法由请求直接提供,例如浏览器中所见网页的截图。这时,无头浏览器就能派上用场。 (headless browser)
无头浏览器是一类提供自动化 API 的特殊浏览器。安装 asyncio reactor 后,可以集成基于 asyncio、负责操作无头浏览器的库。
playwright-python 是这类库之一,也是 playwright 的官方 Python 移植。以下简短示例展示了如何在 Scrapy 爬虫中使用它:
import scrapy
from playwright.async_api import async_playwright
class PlaywrightSpider(scrapy.Spider):
name = "playwright"
start_urls = ["data:,"] # avoid using the default Scrapy downloader
async def parse(self, response):
async with async_playwright() as pw:
browser = await pw.chromium.launch()
page = await browser.new_page()
await page.goto("https://example.org")
title = await page.title()
return {"title": title}
不过,像上述示例一样直接使用 playwright-python,会绕过 Scrapy 的大部分组件,例如中间件和去重过滤器。建议使用 scrapy-playwright,以获得更好的集成。











暂无评论内容