Проблема с Селеном для загрузки всех сообщений, а затем извлечения сообщений

#python #selenium #selenium-webdriver #web-scraping #web-crawler

Вопрос:

Я собираюсь просмотреть этот URL-адрес https://healthunlocked.com/positivewellbeing.

Я написал следующие команды, чтобы сначала нажать на see more posts кнопку, чтобы загрузить все сообщения, а затем извлечь полный текст каждого сообщения. Я пытаюсь запустить код, но это занимает слишком много времени!!! Я запускал код в течение последних 2 дней, и я все еще жду завершения запуска. Я полагаю, что он все еще пытается загружать сообщения через первую часть кода, потому что я еще не видел никаких выходных данных (извлеченных сообщений). Я не знаю, правильно ли я это сделал?

Мой код выглядит следующим образом:

 wait = WebDriverWait(driver, 10)
driver.execute_script("window.scrollTo(0,document.body.scrollHeight);")
## Load all posts
while (driver.find_element_by_xpath('//*[@id="__next"]/main/div[2]/div[1]/div[1]/div[3]/div[31]/button')):
    time.sleep(5)
    driver.find_element_by_xpath('//*[@id="__next"]/main/div[2]/div[1]/div[1]/div[3]/div[31]/button').click()
    

##extract posts 
driver.execute_script("window.scrollTo(0,document.body.scrollHeight);")
time.sleep(3)
lst_post = [x.get_attribute('href') for x in driver.find_elements_by_xpath("//div[@class='results-post']/a")]
for lst in lst_post:
    time.sleep(5)
    driver.get(lst)
    post_body = wait.until(EC.presence_of_element_located((By.XPATH,"/html/body/div[1]/main/div[2]/div[1]/div[1]/div[1]")))
    like_count = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,".post-action--like")))
    #print (ascii(post_body.text))
    print (post_body.text)
    print('‍n')
  
 

Комментарии:

1. Вы используете selenium в безголовом режиме? В противном случае должно открыться новое окно, и вы сможете увидеть, что происходит. То есть вы должны быть в состоянии увидеть, продолжает ли скрипт загружать сообщения или это сделано с его помощью.

2. Не могли бы вы, пожалуйста, проверить цикл «Пока», чтобы убедиться, правильно ли я поступил?

3. Вероятно, нет, driver.find_element_by_xpath() возникает NoSuchElementException исключение, когда он не может найти указанный вами элемент, поэтому я предполагаю, что, если вы дойдете до конца комментариев, сценарий сломается вместо перехода ко второй части

4. спасибо, Вы имеете в виду, что я должен изменить цикл «пока» таким образом? в то время как (драйвер.find_element_by_xpath(‘//*[@id=»__следующий»]/основной/div[2]/div[1]/div[1]/div[3]/div[31]/кнопка’)): попробуйте: время сна(5) driver.find_element_by_xpath(‘//*[@id=»__next»]/main/div[2]/div[1]/div[1]/div[3]/div[31]/button’).click() кроме: перерыв

5. ваш пост #load в то время как цикл никогда не останавливается. Зависит от того, как далеко заходят задние посты… Я могу поделиться своим подходом, если вам это нравится

Ответ №1:

Похоже, этот сайт использует API для получения списка сообщений и получения данных о сообщениях:

список сообщений: https://solaris.healthunlocked.com/posts/positivewellbeing/latest

url сообщения: https://solaris.healthunlocked.com/posts/positivewellbeing/145621054

Используя requests , вы могли бы вызвать эти API, а не использовать selenium, таким образом, это было бы быстрее.

Кроме того, таким образом, вы можете контролировать, когда вы прекратите выскабливание, записав идентификатор последней записи. например, вы можете начать с того места, где вы перестали очищать, если это необходимо.

Следующий код получает все сообщения, созданные в течение последнего месяца, и получает соответствующую информацию:

 import requests
import time
from datetime import datetime, timedelta

allPostUrl = 'https://solaris.healthunlocked.com/posts/positivewellbeing/latest'

now = datetime.today()
postFromTime = now   timedelta(days=-1*30) # last month

fetchAllPost = False
nextPost = ""
posts = []

while not fetchAllPost:
    url = f'{allPostUrl}{f"?createdBeforePostId={nextPost}" if nextPost else ""}'
    print(f"GET {url}")
    r = requests.get(url)
    result = r.json()
    posts.extend(result)
    if len(result) > 0 and nextPost != result[len(result)-1]["postId"]:
        lastCreated = datetime.strptime(result[len(result)-1]["dateCreated"], '%Y-%m-%dT%H:%M:%S.%fZ')
        if lastCreated < postFromTime:
            fetchAllPost = True
        else:
            nextPost = result[len(result)-1]["postId"]
    else:
        fetchAllPost = True

print(f"received {len(posts)} posts")

data = []
for idx, post in enumerate(posts):
    url = f'https://solaris.healthunlocked.com/posts/positivewellbeing/{post["postId"]}'
    print(f"[{idx 1}/{len(posts)}] GET {url}")
    r = requests.get(url)
    result = r.json()
    data.append({
        "body": result["body"],
        "likes": result["numRatings"]
    })

print(data)
 

примерь это repl.it

Комментарии:

1. Большое спасибо за ваш код. Я новичок, не могли бы вы прислать мне ссылку, чтобы узнать, как я могу изменить ваш код, чтобы начать со страницы входа в систему? Потому что мне нужно войти в систему, а затем извлечь сообщения, так как общие сообщения отличаются, когда вы входите на веб-сайт с учетной записью.

2. Кроме того, код возвращает всего 270 сообщений, в то время как на странице более 8000 сообщений

Ответ №2:

мне было скучно, поэтому я сделал свой собственный подход. Он удаляет все ссылки, посещает их, возвращается и удаляет ссылки, которые еще не были посещены

 import time
from selenium import webdriver

from selenium.common.exceptions import ElementClickInterceptedException


driver = webdriver.Chrome()
driver.implicitly_wait(6)
driver.get("https://healthunlocked.com/positivewellbeing/posts")
# click accept cookies
driver.find_element_by_id("ccc-notify-accept").click()
post_links = set()
while True:
    driver.get("https://healthunlocked.com/positivewellbeing/posts")
    all_posts = [post for post in
                 driver.find_element_by_class_name("results-posts").find_elements_by_class_name("results-post") if
                 "results-post" == post.get_attribute("class")]
    # handle clicking more posts
    while len(all_posts) <= len(post_links):

        see_more_posts = [btn for btn in driver.find_elements_by_class_name("btn-secondary")
                          if btn.text == "See more posts"]
        try:
            see_more_posts[0].click()
        except ElementClickInterceptedException:
            # handle floating box covering "see more posts" button
            driver.execute_script("return document.getElementsByClassName('floating-box-sign-up')[0].remove();")
            see_more_posts[0].click()
        all_posts = [post for post in driver.find_element_by_class_name("results-posts").find_elements_by_class_name("results-post") if "results-post" == post.get_attribute("class")]
    # popoulate links
    start_from = len(post_links)
    for post in all_posts[start_from:]: # len(post_links): <-- to avoid visiting same links
        # save link
        link = post.find_element_by_tag_name("a").get_attribute("href")
        post_links.add(link)

    # visit the site and scrape info
    for post_site in list(post_links)[start_from:]:

        driver.get(post_site)
        post_text = driver.find_element_by_class_name("post-body").text
        for btn in driver.find_element_by_class_name("post-actions__buttons").find_elements_by_tag_name("button"):
            if "Like" in btn.text:
                post_like = btn.text.split()[1][1]

        print(f"n{post_text}nLikes -->{post_like}n")
 

Комментарии:

1. Большое спасибо за вашу помощь. Он отлично работает, но возвращает каждое сообщение два раза. Похоже, он извлекает сообщения с самого начала при каждой последующей загрузке.

2. после этого цикла for for post in all_posts[start_from:] набор post_links содержит правильные ссылки