特别是,当我使用
urllib2.urlopen(request)
来读取页面内容时,它不会显示由JavaScript代码添加的任何内容,因为该代码在任何地方都没有被执行。通常它会被网络浏览器运行,但这不是我的程序的一部分。我该如何在我的Python代码中访问这个动态内容呢?
另请参阅Can scrapy be used to scrape dynamic content from websites that are using AJAX?以获取与Scrapy相关的具体答案。
urllib2.urlopen(request)
来读取页面内容时,它不会显示由JavaScript代码添加的任何内容,因为该代码在任何地方都没有被执行。通常它会被网络浏览器运行,但这不是我的程序的一部分。使用PyQt5
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEnginePage
import sys
import bs4 as bs
import urllib.request
class Client(QWebEnginePage):
def __init__(self,url):
global app
self.app = QApplication(sys.argv)
QWebEnginePage.__init__(self)
self.html = ""
self.loadFinished.connect(self.on_load_finished)
self.load(QUrl(url))
self.app.exec_()
def on_load_finished(self):
self.html = self.toHtml(self.Callable)
print("Load Finished")
def Callable(self,data):
self.html = data
self.app.quit()
# url = ""
# client_response = Client(url)
# print(client_response.html)
还有一个选择是playwright-python
,它是将 Microsoft 的 Playwright(它本身是受 Puppeteer 影响的浏览器自动化库)移植到 Python 的版本。
下面是选择元素并获取其文本的最简示例:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("http://whatsmyuseragent.org/")
ua = page.query_selector(".user-agent");
print(ua.text_content())
browser.close()
在爬取数据时,常见的情况是网页通过API端点异步请求数据。以下是一个最简示例:
<body>
<script>
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(res => {
if (!res.ok) throw Error(res.status);
return res.json();
})
.then(data => {
// inject data dynamically via JS after page load
document.body.innerText = data.title;
})
.catch(err => console.error(err))
;
</script>
</body>
一般的步骤是使用浏览器的开发人员工具网络选项卡搜索页面发出的请求,以获取您想要抓取的数据的关键字/子字符串。通常,您会看到一个没有保护的API请求端点,其中包含一个JSON有效负载,您可以直接使用urllib
或requests
模块访问。这就是上面可运行片段的情况,您可以用它来练习。点击“运行片段”后,这是我在网络选项卡中找到端点的方法:
>>> import requests
>>> res = requests.get("https://jsonplaceholder.typicode.com/posts/1")
>>> data = res.json()
>>> data["title"]
'sunt aut facere repellat provident occaecati excepturi optio reprehenderit'
<script>
标签中,可能是JSON字符串或JS对象。例如:
<body>
<script>
var someHardcodedData = {
userId: 1,
id: 1,
title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
body: 'quia et suscipit\nsuscipit recusandae con sequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'
};
document.body.textContent = someHardcodedData.title;
</script>
</body>
获得这些数据没有一种万能的方法。基本技术是使用BeautifulSoup访问<script>
标签文本,然后应用正则表达式或解析来提取对象结构、JSON字符串或任何可能的数据格式。以下是一个概念验证,介绍了上面示例结构的实现:
import json
import re
from bs4 import BeautifulSoup
# pretend we've already used requests to retrieve the data,
# so we hardcode it for the purposes of this example
text = """
<body>
<script>
var someHardcodedData = {
userId: 1,
id: 1,
title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
body: 'quia et suscipit\nsuscipit recusandae con sequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'
};
document.body.textContent = someHardcodedData.title;
</script>
</body>
"""
soup = BeautifulSoup(text, "lxml")
script_text = str(soup.select_one("script"))
pattern = r"title: '(.*?)'"
print(re.search(pattern, script_text, re.S).group(1))
查看以下资源,以解析不完全符合JSON格式的JS对象:
这里有一些额外的案例研究/概念证明,展示了如何使用API绕过网络爬虫的限制:
如果所有其他方法都失败了,请尝试此主题中列出的许多动态爬取库之一。
<script>
块或 API 中提取数据,因此要检查的第一件事是是否可以从页面 JS 使用的相同来源获取该数据。 - ggorlen截至2022年末,Pyppeteer已不再维护;请考虑使用playwright-python作为替代方案。
你可能会考虑使用Pyppeteer,这是一个Chrome/Chromium驱动程序前端Puppeteer的Python移植版。
以下是一个简单的示例,展示了如何使用Pyppeteer访问动态注入到页面中的数据:
import asyncio
from pyppeteer import launch
async def main():
browser = await launch({"headless": True})
[page] = await browser.pages()
# normally, you go to a live site...
#await page.goto("http://www.example.com")
# but for this example, just set the HTML directly:
await page.setContent("""
<body>
<script>
// inject content dynamically with JS, not part of the static HTML!
document.body.innerHTML = `<p>hello world</p>`;
</script>
</body>
""")
print(await page.content()) # shows that the `<p>` was inserted
# evaluate a JS expression in browser context and scrape the data
expr = "document.querySelector('p').textContent"
print(await page.evaluate(expr, force_expr=True)) # => hello world
await browser.close()
asyncio.run(main())
请参阅Pyppeteer的参考文档。
from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options
options = Options()
options.headless = True
browser = Firefox(executable_path="/usr/local/bin/geckodriver", options=options)
url = "https://www.example.com"
browser.get(url)
而 gazpacho是一个非常易于解析渲染HTML的库:
from gazpacho import Soup
soup = Soup(browser.page_source)
soup.find("a").attrs['href']
from requests_html import HTMLSession
session = HTMLSession()
response = session.request(method="get",url="www.google.com/")
response.html.render()
script = """
() => {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
deviceScaleFactor: window.devicePixelRatio,
}
}
"""
>>> response.html.render(script=script)
{'width': 800, 'height': 600, 'deviceScaleFactor': 1}
return {
data: window.view.data
}
requests_html
不再得到积极维护(最后更新于2020年5月)。它使用 pyppeteer
进行渲染,而 pyppeteer
似乎正在积极维护;它在底层使用 Chromium 进行渲染。 - VirtualScooter简单快速的解决方案:
我也遇到了同样的问题。我想要爬取一些由JavaScript构建的数据。如果我仅使用BeautifulSoup从此网站爬取文本,那么在文本中会包含<script>
标签。
我想要渲染这个<script>
标签,并从中获取信息。
另外,我不想使用Scrapy和selenium这样的重型框架。
因此,我发现requests模块的get
方法可以接受URL,并实际上渲染了脚本标签。
示例:
import requests
custom_User_agent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"
url = "https://www.abc.xyz/your/url"
response = requests.get(url, headers={"User-Agent": custom_User_agent})
html_text = response.text
这将渲染加载站点并呈现标签。
希望这能作为快速简便的解决方案,用于呈现包含脚本标签的站点。
<script>
标签的文本和实际执行其中的JS之间存在差异。这只是前者,而不是后者。 - ggorlen