#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)
Комментарии:
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 содержит правильные ссылки