首页 > 解决方案 > 使用 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 登录(我不认为),但抓取正确的网页。在此先感谢您的帮助。

标签: pythonweb-scrapingbeautifulsouphome-assistant

解决方案


您正在使用 requests 模块,这是一个用于抓取静态/服务器端渲染内容的神奇工具。

然而,Cronometer 是一个 javascript 应用程序。如果您禁用 javascript 并尝试加载 cronometer,您将看到“您的网络浏览器必须启用 javascript”消息。

在此处输入图像描述 这基本上也是您的requests电话将看到的内容。

使用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)

推荐阅读