选择动态加载的内容¶
一些网页在浏览器中加载时会显示所需的数据。 然而,当你使用Scrapy下载它们时,你无法通过selectors获取到所需的数据。
当这种情况发生时,推荐的方法是 找到数据源并从中提取数据。
如果你未能做到这一点,但你仍然可以通过你的网页浏览器访问所需的数据,请参阅DOM,参见预渲染JavaScript。
寻找数据源¶
要提取所需的数据,您必须首先找到其源位置。
如果数据是非文本格式,例如图像或PDF文档, 请使用您网页浏览器的网络工具来查找 相应的请求,并重现它。
如果你的网页浏览器允许你选择所需的数据作为文本,那么这些数据可能是在嵌入的JavaScript代码中定义的,或者是从外部资源以文本格式加载的。
在这种情况下,你可以使用像wgrep这样的工具来查找该资源的URL。
如果数据最终来自原始URL本身,你必须 检查网页的源代码 以 确定数据的位置。
如果数据来自不同的URL,您将需要重现相应的请求。
检查网页的源代码¶
有时你需要检查网页的源代码(不是 DOM)来确定某些所需数据的位置。
使用Scrapy的fetch命令来下载Scrapy所看到的网页内容:
scrapy fetch --nolog https://example.com > response.html
如果所需的数据在元素内的嵌入式JavaScript代码中,请参阅解析JavaScript代码。
如果你找不到所需的数据,首先确保这不是Scrapy的问题:使用像curl或wget这样的HTTP客户端下载网页,看看是否可以在它们得到的响应中找到信息。
如果他们得到了包含所需数据的响应,请修改您的Scrapy
Request以匹配其他HTTP客户端的请求。例如,尝试使用相同的用户代理字符串(USER_AGENT)或相同的headers。
如果他们也没有得到所需数据的响应,您需要采取措施使您的请求更类似于网页浏览器的请求。请参阅Reproducing requests。
重现请求¶
有时我们需要以我们的网页浏览器执行请求的方式来重现它。
使用您网页浏览器的网络工具来查看 您的网页浏览器如何执行所需的请求,并尝试使用Scrapy重现该请求。
可能足以生成一个具有相同HTTP方法和URL的Request。然而,您可能还需要复制该请求的主体、头部和表单参数(参见FormRequest)。
由于所有主要浏览器都允许以curl格式导出请求,Scrapy
引入了from_curl()方法,以从cURL命令生成等效的
Request。要获取更多信息,
请访问网络工具部分中的request from curl。
一旦你得到预期的响应,你可以从中提取所需的数据。
你可以使用Scrapy重现任何请求。然而,有时重现所有必要的请求在开发时间上可能看起来并不高效。如果这是你的情况,并且爬取速度对你来说不是主要关注点,你可以考虑JavaScript预渲染。
如果你有时得到预期的响应,但并非总是如此,问题可能不在于你的请求,而在于目标服务器。目标服务器可能存在故障、过载,或者禁止了你的部分请求。
请注意,要将cURL命令转换为Scrapy请求, 你可以使用curl2scrapy。
处理不同的响应格式¶
一旦你得到了包含所需数据的响应,如何从中提取所需数据取决于响应的类型:
如果响应是HTML、XML或JSON,请像往常一样使用selectors。
如果响应是JSON,使用
response.json()来加载所需的数据:data = response.json()
如果所需的数据位于嵌入在JSON数据中的HTML或XML代码中,你可以将该HTML或XML代码加载到
Selector中,然后像往常一样使用它:selector = Selector(data["html"])
如果响应是JavaScript,或者包含所需数据的
元素的HTML,请参阅解析JavaScript代码。如果响应是CSS,使用正则表达式从
response.text中提取所需数据。
如果响应是图像或基于图像的另一种格式(例如PDF), 请从
response.body读取响应为字节,并使用OCR 解决方案将所需数据提取为文本。例如,您可以使用pytesseract。要从PDF中读取表格,tabula-py可能是更好的选择。
如果响应是SVG,或者包含所需数据的嵌入SVG的HTML,您可能能够使用selectors提取所需数据,因为SVG是基于XML的。
否则,您可能需要将SVG代码转换为光栅图像,并 处理该光栅图像。
解析JavaScript代码¶
如果所需的数据在JavaScript中是硬编码的,你首先需要获取JavaScript代码:
如果JavaScript代码在JavaScript文件中,只需读取
response.text。如果JavaScript代码位于HTML页面的
元素内,使用selectors来提取该元素内的文本。
一旦你有了包含JavaScript代码的字符串,你就可以从中提取所需的数据:
你可能可以使用正则表达式来提取JSON格式的所需数据,然后你可以使用
json.loads()来解析它。例如,如果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 提供了一个API,用于将JavaScript对象解析为
dict。例如,如果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>'
预渲染JavaScript¶
在从额外请求中获取数据的网页上,重现包含所需数据的请求是首选方法。这种努力通常值得结果:结构化、完整的数据,具有最少的解析时间和网络传输。
然而,有时重现某些请求可能非常困难。或者你可能需要一些请求无法提供的东西,比如在网页浏览器中看到的网页截图。
在这些情况下,使用Splash JavaScript渲染服务,以及scrapy-splash以实现无缝集成。
Splash 以 HTML 形式返回网页的 DOM,因此你可以使用 selectors 来解析它。它通过 configuration 或 scripting 提供了极大的灵活性。
如果你需要超出Splash提供的功能,例如从Python代码中动态与DOM交互,而不是使用预先编写的脚本,或者处理多个网页浏览器窗口,你可能需要使用无头浏览器来代替。
使用无头浏览器¶
一个无头浏览器是一种特殊的网络浏览器,它提供了用于自动化的API。通过安装asyncio反应器,可以集成基于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以获得更好的集成。