难以从网页中抓取 table

Difficulty scraping a table from a webpage

我一直在尝试使用请求模块从此 webpage 获取表格内容。但是,当我执行下面的脚本时,除了 table:

中可用的结果之外,我得到了一些乱码
import requests
from bs4 import BeautifulSoup

link = 'https://cityofdavid.secure.force.com/?productId=01t0600000AE2dR&lang=en_us'

with requests.Session() as s:
    s.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36'
    resp = s.get(link)
    soup = BeautifulSoup(resp.text,"lxml")
    for item in soup.select("table.table > tbody > tr"):
        data = [i.text for i in item.select("td")]
        print(data)

当前结果:

["{{timeForTour.TourStartTime__c | date: 'dd/MM/yyyy' : '200'}}", '{{timeForTour.TourStartHour__c }}', '{{timeForTour.EndTime__c}}', '{{timeForTour.Number_Of_Participants_Left__c}}', '\nSelect\n\n']
['{{price.Name}}', '{{price.Field1__c}}', '\n\n\n', '{{(price.quantity)? price.Field1__c*price.quantity:0}}', 'NIS']
['{{item.Name}}', '{{item.Field1__c}}', '{{item.quantity}}', '{{item.quantity*item.Field1__c}}', 'NIS']

预期结果(截断):

24/01/2022  10:00   11:00   15
24/01/2022  12:00   13:00   15
24/01/2022  14:00   15:00   15
24/01/2022  16:00   17:00   15

PS 这是我点击 book a tour 按钮获取上述请求中的 link 的 landing page

您不会在页面资源中获取该数据,因为它是动态加载的。如果查看资源页面,您会发现没有您需要的数据。

您可以从 API 调用中获取所需的数据作为 JSON object.Copy curl 命令并将其导入 Postman 并在脚本中使用代码。

在下面的屏幕截图中,您可以看到您想要的实际数据。

已编辑这将给出预期的结果。

import requests
import json

url = "https://cityofdavid.secure.force.com/apexremote"

payload = json.dumps([
  {
    "action": "OrderTourController",
    "method": "getAllTimeForTour",
    "data": [
      "01t0600000AE2dR",
      "2022-1-24 12:18:4",
      0,
      "en_us"
    ],
    "type": "rpc",
    "tid": 3,
    "ctx": {
      "csrf": "VmpFPSxNakF5TWkwd01TMHlOMVF3TmpvME9Eb3dNeTQ1TlRaYSxJZExXMldWSU1neGFlWDdFb01YNi1ULE1XTmpaV1F4",
      "vid": "06624000003gGO2",
      "ns": "",
      "ver": 35
    }
  }
])
headers = {
  'Content-Type': 'application/json',
  'Accept': '*/*',
  'Referer': 'https://cityofdavid.secure.force.com/?productId=01t0600000AE2dR&lang=en_us&_ga=2.113284295.1937905009.1643006618-1494711041.1643006618',
}

response = requests.request("POST", url, headers=headers, data=payload)




json_result=json.loads(json.loads(response.text)[0]['result'])
for res in json_result:
    print(res['TourStartTime__c'], res['TourStartHour__c'] ,res['EndTime__c'],res['MaxNumerOfParticipants__c'])

输出

2022-01-25T08:00:00.000+0000 10:00 11:00 15
2022-01-25T10:00:00.000+0000 12:00 13:00 15
2022-01-25T12:00:00.000+0000 14:00 15:00 15
2022-01-25T14:00:00.000+0000 16:00 17:00 15
2022-01-26T08:00:00.000+0000 10:00 11:00 15
2022-01-26T10:00:00.000+0000 12:00 13:00 15
2022-01-26T12:00:00.000+0000 14:00 15:00 15
2022-01-26T14:00:00.000+0000 16:00 17:00 15
2022-01-27T08:00:00.000+0000 10:00 11:00 15
2022-01-27T10:00:00.000+0000 12:00 13:00 15
2022-01-27T12:00:00.000+0000 14:00 15:00 15
2022-01-27T14:00:00.000+0000 16:00 17:00 15
2022-01-30T08:00:00.000+0000 10:00 11:00 15
2022-01-30T10:00:00.000+0000 12:00 13:00 15
2022-01-30T12:00:00.000+0000 14:00 15:00 15
2022-01-30T14:00:00.000+0000 16:00 17:00 15
2022-01-31T08:00:00.000+0000 10:00 11:00 15
2022-01-31T10:00:00.000+0000 12:00 13:00 15
2022-01-31T12:00:00.000+0000 14:00 15:00 15
2022-01-31T14:00:00.000+0000 16:00 17:00 15

您的问题是最常见的 BeautifulSoup 问题,但实际上并没有很好的答案 - 您的页面已使用 JavaScript 更新,您需要使用 selenium或类似的东西让网页在抓取之前完成加载和执行JavaScript。

您的 s.get(link) 检索到的 HTML 具有 JavaScript 动态填充 table 所需的所有信息,但还没有您要抓取的实际数据。

由于requests不能运行 JavaScript,您需要将其加载到实际的浏览器中,例如由浏览器驱动的无头浏览器driver 通过 selenium.

您可以选择任何风格的浏览器,但例如,要使其与 Firefox 一起使用,您可以从 https://github.com/mozilla/geckodriver/releases 下载 gecko-driver 并将其放入您的项目中(例如 bin文件夹)。同时安装 seleniumpip install selenium(假设您使用 pip

您的代码将是:

from os import environ
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
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

# selenium needs the driver to be on the path, so adding that location
environ['path'] = f'bin;{environ["path"]}'

# set headless to False if you want to see what happens, 
# but to True for operation in the background
options = Options()
options.headless = True

# this expects `geckodriver.exe` to be on the path
driver = webdriver.Firefox(options=options)
# start loading the page
driver.get('https://cityofdavid.secure.force.com/?productId=01t0600000AE2dR&lang=en_us')

# make sure to keep waiting, until an element appears with the class `timer-message`
# note that this is different for each page, and may change - 
# just look for something that doesn't appear on the page until it is done loading
# or, if there is nothing like it, use some sort of delay like `time.sleep`
WebDriverWait(driver, 10).until(ec.presence_of_element_located((By.CLASS_NAME, 'timer-message')))

# the below uses `driver.page_source` to stay close to your code
soup = BeautifulSoup(driver.page_source, "lxml")
for item in soup.select("table.table > tbody > tr"):
    data = [i.text for i in item.select("td")]
    print(data)

请注意,driver 有自己的方法来访问实时页面中的元素,这可能比使用 bs4 更好 - 请查看文档 https://selenium-python.readthedocs.io/locating-elements.html

如果您更喜欢完全自动化的解决方案并且不想捆绑二进制文件,则可以使用 webdriver-manager 之类的库来代替自动化安装过程。

请注意,您可能会找到更简单的解决方案,建议您执行以下操作:

driver = webdriver.Firefox(executable_path='bin/geckodriver.exe')
driver.get('http://google.com')

这仍然有效,但 selenium 告诉您这种做事方式已被弃用,所以不要指望它会无限期地有效。

最后一句警告:大多数网站不喜欢他们的内容被抓取。在某些情况下,动态加载他们的内容是他们通过默默无闻获得安全性的方式,上面的内容允许您规避这种情况 - 确保您正在做的事情确实没问题。

一种更轻量级的方法(站点管理员可能会或可能不会接受)是找出 JS 加载其数据的位置并直接加载。以您的页面为例,URL https://cityofdavid.secure.force.com/apexremote 将仅以 JSON 加载数据,但需要您提供正确的 header 和 cookie,这可能需要有些人想出正确的方法(并且可能需要你先点击一个实际的网页,以获得 session cookie)。 @ManishShetty 建议的答案提供了一些关于如何开始的见解。如果您只是在此处关注 link,您将被拒绝访问。

最佳 方法总是检查 API 并在可用时使用它。如果不是,请遵守该站点的规则,否则后果自负。如果您的 IP 获得 black-listed - 或更糟,请不要怪我。

您得到的是乱码数据,因为内容是由 Javascript 生成的 ,要处理此问题,您需要硒或任何类似的东西来获取 Javascript 数据,而不是请求,您可以使用硒来获取页面源代码,然后像以前一样继续使用 soup

import time

from bs4 import BeautifulSoup
from selenium import webdriver
from webdriver_manager.microsoft import EdgeChromiumDriverManager

driver = webdriver.Edge(EdgeChromiumDriverManager().install())

driver.get('https://cityofdavid.secure.force.com/?productId=01t0600000AE2dR&lang=en_us')
time.sleep(5)
htmlSource = driver.page_source
soup = BeautifulSoup(htmlSource, "lxml")
for item in soup.select("table.table > tbody > tr"):
    data = [i.text for i in item.select("td")]
    print(data)

请问return您可以根据需要使用以下数据

['24/01/2022', '10:00', '11:00', '15', '\nSelect\n\n']
['24/01/2022', '12:00', '13:00', '15', '\nSelect\n\n']
['24/01/2022', '14:00', '15:00', '15', '\nSelect\n\n']
['24/01/2022', '16:00', '17:00', '15', '\nSelect\n\n']
['25/01/2022', '10:00', '11:00', '11', '\nSelect\n\n']
['25/01/2022', '12:00', '13:00', '15', '\nSelect\n\n']
['25/01/2022', '14:00', '15:00', '15', '\nSelect\n\n']
['25/01/2022', '16:00', '17:00', '15', '\nSelect\n\n']
['26/01/2022', '10:00', '11:00', '14', '\nSelect\n\n']
['26/01/2022', '12:00', '13:00', '15', '\nSelect\n\n']
['26/01/2022', '14:00', '15:00', '15', '\nSelect\n\n']
['26/01/2022', '16:00', '17:00', '15', '\nSelect\n\n']
['27/01/2022', '10:00', '11:00', '12', '\nSelect\n\n']
['27/01/2022', '12:00', '13:00', '11', '\nSelect\n\n']
['27/01/2022', '14:00', '15:00', '15', '\nSelect\n\n']
['27/01/2022', '16:00', '17:00', '15', '\nSelect\n\n']
['30/01/2022', '10:00', '11:00', '15', '\nSelect\n\n']
['30/01/2022', '12:00', '13:00', '15', '\nSelect\n\n']
['30/01/2022', '14:00', '15:00', '15', '\nSelect\n\n']
['30/01/2022', '16:00', '17:00', '15', '\nSelect\n\n']