python - 使用 BeautifulSoup 抓取 Cronometer.com 时遇到问题
问题描述
我对 Python 很陌生,但是使用一些不同的在线指南,我设法将一些代码拼接到一个名为 cronometer.com 的网站(健康跟踪网站/应用程序,类似于 myfitnesspal)。不幸的是,我在抓取任何数据时遇到了麻烦。
我有以下代码(忽略 Hass/AppDaemon,我在 Home Assistant 中运行这个 python 脚本):
import appdaemon.plugins.hass.hassapi as hass
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
import requests
class Scraper(hass.Hass):
def initialize(self):
self.log("Scraper Initialized")
self.get_values(self)
def get_values(self,kwargs):
self.login_url = "https://cronometer.com/login/"
self.r = requests.get(self.login_url)
self.bs = BeautifulSoup(self.r.text, 'html.parser')
self.csrf_token = self.bs.find('input', attrs={'name': 'anticsrf'})['value']
self.url = "https://cronometer.com/"
self.session = requests.Session()
self.payload = {
"username": "MY_USERNAME",
"password": "MY_PASSWORD",
"anticsrf": self.csrf_token
}
self.headers = {'referer': self.login_url, 'User-agent': 'Chrome'}
self.sensorname = "sensor.scraper"
self.friendly_name = "Fasting Status"
try:
s = self.session.post(self.login_url, data=self.payload, headers=self.headers, cookies=self.r.cookies)
except:
self.log("Could not log in")
return
self.log(self.csrf_token)
s = self.session.get(self.url)
page = s.content
soup = BeautifulSoup(page, "html.parser")
# Test 1
fasting1 = soup.select('#cronometerApp > div:nth-child(2) > div:nth-child(1) > div > table > tbody > tr > td:nth-child(1) > div > div:nth-child(8) > div > div.diary-item-title > div')
self.log("TEST 1")
self.log(fasting1)
# Test 2
fasting2 = soup.select('#cronometerApp > div:nth-child(2) > div:nth-child(1) > div > table > tbody > tr > td:nth-child(1) > div > div:nth-child(8) > div > div.diary-item-content > div.GJES3IWDERB')
self.log("TEST 2")
self.log(fasting2)
# Test 3
fasting3 = soup.select('#w-node-dd7aab6f-acfc-dfa1-2372-313b5d39fc2b-0dd15747 > div.div__mobile__features-text-1 > h5')
self.log("TEST 3")
self.log(fasting3)
# Test 4
fasting4 = soup.select('#cronometerApp > div:nth-child(2) > div:nth-child(1) > div > table > tbody > tr > td:nth-child(2) > div > div.GJES3IWDHFD > button:nth-child(1) > span')
self.log("TEST 4")
self.log(fasting4)
# Test 5
fasting5 = soup.select('#cronometerApp > div:nth-child(2) > div:nth-child(1) > div > table > tbody > tr > td:nth-child(2) > div > div.diary_side_box.GJES3IWDIQB > div.GJES3IWDKQB > div > div.GJES3IWDITE > table > tbody > tr > td > div:nth-child(1) > span')
self.log("TEST 5")
self.log(fasting5)
self.set_state(self.sensorname, state= "Test", attributes = {"friendly_name": self.friendly_name})
据我所知,此代码成功登录 cronometer.com,没有任何问题。问题是(我认为)我个人主页的 URL 与登录前网站的 URL 相同。因此,在使用session.post
将我的凭据发送到网站之后,我正在使用session.get
从我的“个人资料”中抓取数据。但它只是从普通的 cronometer.com 网页(在你登录之前)抓取数据,而不是我自己的具有相同 URL 的个人网页。
我确实注意到的一件事是,当我单击顶部的选项卡时,URL 确实略有变化,如您在此处看到的:
当我单击 Diary 时,URL 从 cronometer.com 变为 cronometer.com/#diary,而 Trends 为 cronometer.com/#trends,依此类推。但是使用这些特定的 URL 也没有被证明是富有成效的。
再次抱歉,我缺乏知识,但我该如何克服这个问题?我已经尝试查看一些有关 Selenium 的在线指南,但到目前为止,当问题不一定是登录时,我无法理解如何使用 Selenium 登录(我不认为),但抓取正确的网页。在此先感谢您的帮助。
解决方案
您正在使用 requests 模块,这是一个用于抓取静态/服务器端渲染内容的神奇工具。
然而,Cronometer 是一个 javascript 应用程序。如果您禁用 javascript 并尝试加载 cronometer,您将看到“您的网络浏览器必须启用 javascript”消息。
使用requests-html 模块和selenium等工具抓取这样的网站是一项简单的任务。
我个人喜欢 selenium,因为它非常易于使用,而且您实际上可以在 chrome 浏览器中实时看到脚本在做什么。
我写了一段代码,登录到 cronometer 并抓取每日能量值。我添加了注释来解释每一行的作用。
import chromedriver_autoinstaller
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
# setup selenium (I am using chrome here, so chrome has to be installed on your system)
chromedriver_autoinstaller.install()
options = Options()
# if you set this to true the chrome window will not be displayed
options.headless = False
driver = webdriver.Chrome(options=options)
URL = 'https://cronometer.com/login/'
USERNAME = ''
PASSWORD = ''
# navigate to cronometer
driver.get(URL)
# fill inputs
driver.find_element(by=By.NAME, value='username').send_keys(USERNAME)
driver.find_element(by=By.NAME, value='password').send_keys(PASSWORD)
# click on the login button
driver.find_element(by=By.ID, value='login-button').click()
# wait until the daily energy bar loads, or skip if 10 seconds have passed
timeout = 10
expectation = EC.element_to_be_clickable((By.CSS_SELECTOR, '.nutrientTargetBar-text'))
nutrients_element = WebDriverWait(driver, timeout).until(expectation)
# print daily energy bar text
print(nutrients_element.text)