我试图开发一个简单的网页刮板。我想提取没有HTML代码的文本。它适用于普通HTML,但不适用于JavaScript代码添加文本的某些页面。

例如,如果一些JavaScript代码添加了一些文本,我不能看到它,因为当我调用:

response = urllib2.urlopen(request)

我得到了原始文本而没有添加的文本(因为JavaScript是在客户端执行的)。

所以,我正在寻找一些解决这个问题的想法。


当前回答

如前所述,Selenium是呈现JavaScript结果的好选择:

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']

其他回答

把BeautifulSoup和Selenium混合在一起对我来说效果很好。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup as bs

driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
    try:
        element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "myDynamicElement"))) #waits 10 seconds until element is located. Can have other wait conditions  such as visibility_of_element_located or text_to_be_present_in_element

        html = driver.page_source
        soup = bs(html, "lxml")
        dynamic_text = soup.find_all("p", {"class":"class_name"}) #or other attributes, optional
    else:
        print("Couldnt locate element")

附注:你可以在这里找到更多的等待条件

尝试直接访问API

在抓取中常见的场景是网页从API端点异步请求数据。一个最小的例子是以下网站:

身体< > < >脚本 fetch(“https://jsonplaceholder.typicode.com/posts/1”) .then(res => { if (!res.ok)抛出错误(res.status); 返回res.json (); }) .then(data => { //页面加载后通过JS动态注入数据 document.body.innerText = data.title; }) .catch(err => console.error(err)) ; > < /脚本 身体< / >

在许多情况下,API将受到CORS或访问令牌的保护,或速率限制过高,但在其他情况下,它是公开可访问的,您可以完全绕过网站。对于CORS问题,你可以在任何地方尝试CORS。

一般的过程是使用浏览器的开发人员工具的网络选项卡来搜索页面发出的请求,以获得您想要抓取的数据的关键字/子字符串。通常,您会看到一个不受保护的API请求端点,该端点带有一个JSON有效负载,您可以直接使用urllib或请求模块访问该有效负载。上面的可运行代码片段就是这种情况,你可以用它来练习。点击“运行片段”后,下面是我如何在我的网络选项卡中找到端点:

这个例子是虚构的;从静态标记来看,端点URL可能不明显,因为它可以被动态组装、缩小并隐藏在数十个其他请求和端点之下。网络请求还将显示任何相关的请求有效负载细节,例如您可能需要的访问令牌。

在获取端点URL和相关细节后,使用标准HTTP库在Python中构建一个请求并请求数据:

>>> 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'

当你可以摆脱它时,这往往比使用Selenium、Pyppeteer、Scrapy或其他流行的抓取库更容易、更快、更可靠。

如果您很不幸,数据没有通过API请求以良好的格式返回数据,那么它可能是原始浏览器负载的一部分,在<script>标记中,作为JSON字符串或(更可能的是)JS对象。例如:

<body> <script> var someHardcodedData = { 用户 ID: 1, 编号: 1, 题目: “Sunt aut facere repellat provident occaecati excepturi optio reprehenderit”, Body: 'quia et suscipit\nsuscipit recusandae con sequuntur expedita et\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))

看看下面这些解析JS对象的资源,它们不是很有效的JSON:

如何将原始javascript对象转换为python字典? 如何修复JSON键值没有双引号?

以下是一些使用API绕过抓取的额外案例研究/概念证明:

如何使用Python beautifulsoup将yelp评论和星级评分刮到CSV Beautiful Soup对现有元素返回None 从BeautifulSoup Python中提取数据 通过POST收集Bandcamp粉丝(使用一种混合方法,即向网站发出初始请求,从使用BeautifulSoup的标记中提取一个令牌,然后在对JSON端点的第二个请求中使用)

如果所有这些都失败了,请尝试本线程中列出的许多动态抓取库中的一个。

简单快捷的解决方案:

我也遇到过同样的问题。我想刮一些数据是用JavaScript构建的。如果我只用BeautifulSoup从这个网站抓取文本,那么我就以文本中的标签结束。 我想渲染这个标签,并将从中抓取信息。 另外,我不想使用像Scrapy和selenium这样的笨重框架。

我发现请求模块的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

这将呈现加载站点和呈现标签。

希望这将有助于作为快速和简单的解决方案,渲染网站加载脚本标签。

Playwright-Python

还有一种选择是剧作家- Python,它是微软剧作家(本身是受木偶大师影响的浏览器自动化库)到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()

Selenium是抓取JS和Ajax内容的最佳工具。

查看这篇文章,了解如何使用Python从web中提取数据

$ pip install selenium

然后下载Chrome webdriver。

from selenium import webdriver

browser = webdriver.Chrome()

browser.get("https://www.python.org/")

nav = browser.find_element_by_id("mainnav")

print(nav.text)

容易,对吧?