难以从网页中抓取 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
文件夹)。同时安装 selenium
和 pip 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']
我一直在尝试使用请求模块从此 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
文件夹)。同时安装 selenium
和 pip 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']