123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603 |
- from selenium import webdriver
- from selenium.webdriver.common.by import By
- from selenium.webdriver.common.keys import Keys
- from webdriver_manager.chrome import ChromeDriverManager
- import lxml.html
- from lxml import etree
- from bs4 import BeautifulSoup
- from fake_useragent import UserAgent
- from textblob import TextBlob
- import time
- import re
- import csv
- import random
- from pathlib import Path
- import config
- from database import DataBase
- class Browser:
- """ Класс представляющий браузер """
- def get_browser(self):
- """ Настроить и получить браузер """
- user_agent = UserAgent()
- user_agent = user_agent.opera
- driver = ChromeDriverManager().install()
- options = webdriver.ChromeOptions()
- options.add_argument(f'--user-agent={user_agent}')
- options.add_argument(f'--disable-blink-features=AutomationControlled')
- #options.add_argument('--headless')
- browser = webdriver.Chrome(
- driver,
- options=options
- )
- return browser
- def launch_browser(self, url, time=5):
- """ Запустить браузер """
- self.browser.get(url)
- self.browser.maximize_window()
- self.browser.implicitly_wait(time)
- def close_browser(self):
- """ Закрыть браузер """
- self.browser.close()
- class Parser(Browser):
- """ Класс представляющий парсер """
- def __init__(self):
- self.home_page = 'https://stackoverflow.com/questions/'
- self.browser = self.get_browser()
- self.launch_browser(url=self.home_page)
- self.parsing_process()
- def convert_to_bs(self):
- """ Получить объект BeautifulsSoup из Selenium """
- html_source = self.browser.page_source
- page = BeautifulSoup(html_source, 'lxml')
- return page
- def get_bs_object(self, file_name):
- """ Открыть сохранненый файл страницы и вернуть его в виде объекта bs """
- page_file = Path('stackoverflow_html/')
- page_file = page_file/file_name
- with open(page_file, 'r', encoding='utf-8') as file:
- text = file.read()
- page = BeautifulSoup(text, 'lxml')
- return page
- def wait(self, t=2.6):
- """ Чуть-чуть подождать """
- t = random.uniform(1, t)
- print(f'Ждем {t}\n')
- time.sleep(t)
- def write_to_csv(self, data):
- """ Записать переданные данные в csv файл """
- with open('data.csv', 'a') as file:
- writer = csv.writer(file)
- def close_banners(self):
- """ Закрываем баннеры """
- self.browser.find_element(
- By.XPATH,
- '//div[@class="ff-sans ps-fixed z-nav-fixed ws4 sm:w-auto p32 sm:p16 bg-black-750 fc-white bar-lg b16 l16 r16 js-consent-banner"]//div[@class="grid gs8 ai-stretch fd-column sm:fd-row"]/button[1]'
- ).click()
- self.browser.implicitly_wait(5)
- def parsing_process(self):
- """ Весь процесс парсинга """
- def go_to_next_page(page_number):
- """ Перейти на следующую страницу вопросов """
- try:
- link = f'https://stackoverflow.com/questions?tab=newest&page={page_number}'
- self.launch_browser(url=link)
- except:
- return 0
- page_number = 1
- while True:
- links_all_questions = self.get_links_all_questions() # Получаем список вопросов со страницы
- for i, question_link in enumerate(links_all_questions, 1):
- print(f'Вопрос #{i} страницы {page_number}')
- print(f'Его ссылка - {question_link}')
- self.launch_browser(url=question_link) # Открываем первый вопрос
- well_waht = self.get_content_question()
- if not well_waht:
- print('У этого вопроса нет ответов')
- self.wait()
- page_number += 1
- try:
- go_to_next_page(page_number) # Переходим на следующую страницу
- except:
- print('Такой страницы нет')
- break
- print(f'\nПарсинг закончен')
- return
- def get_links_all_questions(self):
- """ Получить ссылки на все вопросы """
- links = []
- temp = ['https://stackoverflow.com/questions/67506798/reformat-dataframe-add-rows-when-condition-is-met']
- question_links = self.browser.find_elements(
- By.XPATH,
- '//div[@id="questions"]/div[@class="question-summary"]//h3/a'
- )
- for question_link in question_links:
- address = question_link.get_attribute('href')
- links.append(address)
- return temp
- def translate_text(self, text):
- """ Переводим полученный текст и возвращаем его """
- def cut_out_excess(text):
- """ Вырезать лишние теги, которые не требуются в вопросе """
- result = re.sub(r'<aside.*?>.*?</aside>', '', str(text), flags=re.S)
- return result
- def cut_code_from_question(text):
- """ Получаем все блоки кода из вопроса и возврощаем их """
- cut_out_code = re.findall(r'<pre.+?>.+?</pre>|<hr/>|<a.+?>.+?</a>|<code.*?>.*?</code>|<blockquote.*?>.*?</blockquote>', text, flags=re.S)
- return cut_out_code
- def get_translated_text(text):
- """ Переводим текст """
- blob = TextBlob(text)
- trans = blob.translate(to='ru')
- return trans
- def remove_unnecessary_parts(text):
- """ Удаляем части текста, переводить которые не нужно.
- Возвращаем готовый текст для перевода """
- cleared_text = re.sub(r'<pre.+?>.+?</pre>|<hr/>|<a.+?>.+?</a>|<code.*?>.*?</code>|<blockquote.*?>.*?</blockquote>', 'Ё', str(text), flags=re.S)
- return cleared_text
- def insert_code_in_text(text):
- """ Вставляем код в переведенный текст """
- done_text = []
- count = 0
- text = text.split('Ё')
- for part in text:
- if count == len(cut_out_code):
- if len(text) != len(cut_out_code):
- done_text.append(text[-1])
- result = ''.join(done_text)
- return result
- done_text.append(part + cut_out_code[count])
- count += 1
- text = cut_out_excess(text) # Очищенный текст
- cut_out_code = cut_code_from_question(text) # Блоки кода из вопроса
- cleared_text = remove_unnecessary_parts(text) # Получаем 'чистый' текст для перевода
- translated_text = get_translated_text(cleared_text) # Получаем переведенный текст
- done = insert_code_in_text(translated_text) # Готовый вопрос
- return done
- def get_content_question(self):
- """ Собираем информацию со всего вопроса """
- def get_question_title():
- """ Получить заголовок вопроса """
- question_title = content.find('div', {'id': 'question-header'}) \
- .find('h1').find('a').text
- result = self.translate_text(question_title)
- return result
- def get_question_link():
- """ Получить ссылку вопроса """
- question_link = content.find('div', {'id': 'question-header'}) \
- .find('h1').find('a')['href']
- result = 'https://stackoverflow.com' + question_link
- return result
- def get_activity_information():
- """ Получить значения об активности вопроса """
- activity = page.find('div', {'class': 'inner-content'})
- # Вопрос задан
- question_asked = activity.find('time', {'itemprop': 'dateCreated'}).text
- # Последняя активность
- last_activity = activity.find('a', {'class': 's-link'}).text
- # Количество просмотров
- viewed = activity.find_all('span', {'class': 'fc-light'})[2] \
- .parent.text
- viewed = re.findall(r'\d+', viewed)[0]
- data = (question_asked, last_activity, viewed)
- return data
- def get_upvote_count():
- """ Получить количество голосов вопроса """
- upvote_count = full_question.find('div', {'itemprop': 'upvoteCount'}).text
- return upvote_count
- def get_question_content():
- """ Получить основное содержание вопроса """
- content = full_question.find('div', {'class': 'postcell'}) \
- .find('div', {'class': 'js-post-body'})
- translated_text = self.translate_text(content) # Переведенное содержание вопроса
- return translated_text
- def get_question_tags():
- """ Получить все теги вопроса """
- def get_description_tags(tag):
- """ Получить описание тега """
- link = f'https://stackoverflow.com/questions/tagged/{tag}'
- self.launch_browser(link)
- tag_description = self.browser.find_element(
- By.XPATH,
- '//div[@id="mainbar"]//p'
- ).text
- result = self.translate_text(tag_description)
- return result
- print('Смотрим теги вопроса')
- done_list_tags = []
- tags = full_question.find_all('a', {'class': 'post-tag'})
- for tag in tags:
- tag_name = tag.text
- tag_desc = get_description_tags(tag_name)
- done_list_tags.append([tag_name, tag_desc])
- return done_list_tags
- def get_related_questions():
- """ Получить похожие вопросы """
- result = []
- q_related = page.find('div', {'class': 'sidebar-related'}) \
- .find_all('div', {'class': 'spacer'})
- for q in q_related:
- vote_related_q = q.find_all('a')[0].text.strip()
- text_related_q = q.find_all('a')[1].text
- translated_text_q = self.translate_text(text_related_q) # Переводим текст
- href_related_q = q.find_all('a')[1]['href']
- result.append((vote_related_q, translated_text_q, href_related_q))
- return result
- def get_question_comments():
- """ Получить комментарии к вопросу """
- try:
- # Закрываем все возможные банеры
- #self.launch_browser(url='https://stackoverflow.com/questions/17778372/why-does-my-recursive-function-return-none')
- self.browser.find_element(
- By.XPATH,
- '/html/body/div[5]/div/button[1]'
- ).click()
- self.browser.implicitly_wait(5)
- self.browser.find_element(
- By.XPATH,
- '//*[@id="openid-buttons"]/button[4]'
- ).click()
- self.browser.implicitly_wait(5)
- # Нажимаем кнопку показать больше
- btn = self.browser.find_element(
- By.XPATH,
- '//div[@class="question"]//a[@class="js-show-link comments-link "]'
- )
- btn.click()
- time.sleep(2.5)
- except Exception as ex:
- # Если баннеров или вопросов больше нет - ничего не делаем
- pass
- result = []
- comments = page.find('div', {'class': 'js-post-comments-component'}) \
- .find('ul', {'class': 'comments-list'}) \
- .find_all('li')
- for comment in comments:
- c_actions = comment.find('div', {'class': 'comment-actions'}).text.strip()
- c_text = comment.find('div', {'class': 'js-comment-text-and-form'}) \
- .text.strip()
- c_text = re.split(r'\w{3}\s\d+\s\'\d+\sat\s', c_text)[0]
- c_text_done = self.translate_text(c_text)
- result.append([c_actions, c_text_done])
- return result
- def get_question_answer():
- """ Получить все ответы к вопросу """
- def get_answer_votes(answer):
- """ Получить количество голосов ответа """
- number_votes = answer.find('div', {'class': 'votecell'}) \
- .find('div', {'class': 'js-vote-count'}).text.strip()
- return number_votes
- def get_text_answer(answer):
- """ Получить текст ответа перевести его """
- text = answer.find('div', {'class': 'answercell'}) \
- .find('div', {'class': 'js-post-body'})
- translated_text = self.translate_text(text) # Получаем переведенный текст
- return translated_text
- def get_content_answer(all_answers, answer_from_page):
- """ Получаем содержимое ответа """
- for i, answer in enumerate(answer_from_page, 1):
- print(f'Ответ №{i}')
- number_votes = get_answer_votes(answer)
- text_answer = get_text_answer(answer)
- all_answers.append([number_votes, text_answer])
- def get_number_pages_with_answers():
- """ Проверяем есть страницы с ответами еще """
- result = []
- try:
- pagination = self.browser.find_elements(
- By.XPATH,
- '//div[@class="s-pagination pager-answers"][1]/*'
- )
- except: # Значит страниц, с дополнительными ответами нет
- return 0
- number_elements = len(pagination) - 1
- for n in range(1, number_elements + 1):
- link = f'https://stackoverflow.com/questions/11/calculate-relative-time-in-c-sharp?page={n}&tab=votes#tab-top'
- result.append(link)
- return result[1:]
- print('Смотрим ответы к вопросу')
- all_answers = []
- page = self.convert_to_bs()
- answer_from_page = page.find('div', {'id': 'answers'}) \
- .find_all('div', {'class': 'answer'}) # Все ответы с вопроса
- if answer_from_page == 0:
- return 0
- get_content_answer(all_answers, answer_from_page) # Достаем все из вопроса
- additional_p = get_number_pages_with_answers() # Узнаем, есть ли еще страницы с вопросами
- if additional_p: # Если есть дополнительные страницы с ответами
- for p in additional_p:
- self.launch_browser(p)
- page = self.convert_to_bs()
- answer_from_page = page.find('div', {'id': 'answers'}) \
- .find_all('div', {'class': 'answer'}) # Все ответы с вопроса
- get_content_answer(all_answers, answer_from_page) # Достаем все из вопроса
- return all_answers
- # НИЖЕ ВЫЗОВ ВСЕХ ФУНКЦИЙ
- page = self.convert_to_bs()
- content = page.find('div', {'id': 'content'})
- full_question = content.find('div', {'id': 'mainbar'}) \
- .find('div', {'class': 'question'})
- q_answer = get_question_answer()
- print(q_answer)
- print(type(q_answer))
- print(len(q_answer))
- for item in q_answer:
- print(f'Голосов {item[0]}\n{item[1]}')
- if not q_answer: # Если нет ответов, то и вопрос этот не нужен
- return
- self.browser.implicitly_wait(5)
- q_title = get_question_title()
- q_link = get_question_link()
- q_activity = get_activity_information()
- q_upvote_count = get_upvote_count()
- #q_content = get_question_content()
- #q_tags = get_question_tags()
- q_related = get_related_questions()
- q_comments = get_question_comments()
- db = DataBase(
- title=q_title,
- link=q_link,
- upvote=q_upvote_count,
- related_questions=q_related,
- answers=q_answer
- )
- a = Parser()
|