Я пишу скрипт на Python, чтобы ввести слово в онлайн-программу латинского морфологического анализа Collatinus (Collatinus) и получить полное склонение/спряжение этого слова.
Вот что у меня есть на данный момент:
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium import webdriver
import time
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
import pandas
from bs4 import BeautifulSoup as bs
import sys
#x = input("Word:")
chrome_service = Service(executable_path = "C:\Program Files (x86)\msedgedriver.exe")
driver = webdriver.Edge(service=chrome_service)
driver.get("https://outils.biblissima.fr/en/collatinus-web/")
time.sleep(15)
driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.COMMAND + 'r')
time.sleep(15)
driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.COMMAND + 'r')
time.sleep(15)
search = driver.find_element(By.ID, "flexion_lemme")
search.send_keys('canis')
time.sleep(7)
submit = driver.find_elements(By.TAG_NAME, "button")
correct = submit[4]
correct.click()
time.sleep(15)
html = driver.page_source
if html.find("Une erreur s'est produite") != -1:
driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.COMMAND + 'r')
time.sleep(20)
search = driver.find_element(By.ID, "flexion_lemme")
search.send_keys('canis')
time.sleep(7)
submit = driver.find_elements(By.TAG_NAME, "button")
correct = submit[4]
correct.click()
time.sleep(20)
html = driver.page_source
if html.find("Une erreur s'est produite") != -1:
driver.quit()
raise ZeroDivisionError("Nope.")
else:
results = driver.find_element(By.ID, "results")
titlesh4 = results.find_elements(By.TAG_NAME, "h4")
titlesp = results.find_elements(By.TAG_NAME, "p")
titleshref = results.find_elements(By.XPATH, "//*[ text() = 'Formes composées' ]")
html = driver.page_source
f = open('tables.html', "w", encoding = "utf-8")
f.write(html)
f.close()
lh = open("tables.html", "r", encoding = "utf-8")
soup = bs(lh, "html.parser")
prettyHTML=soup.prettify()
prettyHTML = prettyHTML.replace("ā", "a").replace("ă","a").replace("ā̆", "a").replace("ē","e").replace("ĕ", "e").replace("ē̆","e").replace("ī", "i").replace("ĭ", "i").replace("ī̆","i").replace("ō","o").replace("ō̆","o").replace("ŏ","o").replace("ŭ","u").replace("ū̆", "u").replace("ū","u")
f = open('tables.html', "w", encoding = "utf-8")
f.write(prettyHTML)
f.close()
Он все еще находится в разработке.
Проблема в том, что речь идет о заголовках каждой таблицы, которую я извлекаю из функции.
Я хочу, чтобы каждая таблица данных, которую я извлекаю из HTML, имела заголовок, с которым я мог бы работать. Но заголовки, которые дает программное обеспечение, имеют разные формы: h4, p, a и т. д. Кроме того, некоторые заголовки перекрывают другие в иерархии, например. заголовок таблицы «subjonctif» будет иметь другой заголовок, скажем, «actif» или что-то в этом роде.
На мой взгляд, единственный способ сделать это - это если бы программа могла обнаружить заголовок перед ним и принять решение на его основе.
А еще эти иерархические; Я бы хотел, чтобы родительское имя было включено в каждый из меньших заголовков, т. е. «subjonctif» стало бы «actif subjonctif».
Последняя проблема заключается в том, что некоторые заголовки содержат внутри себя две или три (?) таблицы, поэтому мне бы хотелось, чтобы их можно было помечать, например, как «подчиненный номер #1» и «подчинительный знак #2».
На мой взгляд (поправьте меня, если я ошибаюсь) все эти проблемы можно было бы легко исправить, если бы программа знала, что находится перед каждой таблицей.
Я особо ничего не пробовал, так как не знаю, с чего начать.
Если бы кто-нибудь мог помочь, это было бы очень признательно.
Первое, что я вам посоветую, это отказаться от selenium
, здесь его использовать не нужно. Чтобы успешно парсить множество сайтов, нужно перестать думать как человек, думать как браузер. Я написал простой scraper
, который обходится без selenium
, просто анализирует необходимый token
для POST
запрос к этой конечной точке и получает данные. Конечным результатом является то, что мы делаем первоначальный запрос, cookies
сохраняются, получаем token
и можем работать с ним. Паук выглядит следующим образом:
from typing import NamedTuple
import requests
from bs4 import BeautifulSoup
class FormData(NamedTuple):
opera: str
token: str
def to_string(self) -> str:
return f'opera = {self.opera}&token = {self.token}'
class Scraper:
def __init__(self, form_option: str = 'flexion') -> None:
self.session = None
self.form_data = None
self.form_option = form_option
def __enter__(self):
start_url = 'https://outils.biblissima.fr/en/collatinus-web/'
response = requests.get(start_url)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'lxml')
input_tags = soup.find_all('input', attrs = {'type': "hidden"})
prev_input_value = ''
for inp in input_tags:
if prev_input_value == self.form_option:
self.form_data = FormData(
opera=prev_input_value,
token=inp['value'],
)
break
if inp['name'] == 'opera':
prev_input_value = inp['value']
self.session = requests.session()
self.session.cookies = response.cookies
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.session.close()
def parse_word(self, target_word: str):
if self.form_data is None or self.session is None:
raise RuntimeError('invalid initialization')
target_url = (
'https://outils.biblissima.fr/collatinus-web/collatinus-web.php'
'/collatinus-web.php'
)
headers = {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
}
response = self.session.post(
target_url,
headers=headers,
data=f'lemme = {target_word}&{self.form_data.to_string()}'
)
response.raise_for_status()
return _parse_html(response.text)
Токен для вашей формы можно найти здесь:
Для работы вам понадобятся следующие библиотеки: request
, beautifulsoup4
, lxml
. Установите их с помощью pip
или другого менеджера пакетов. spider
все сделает сам при первом подключении, он разберет token
, установит cookies
, чтобы вы могли его использовать, сделайте следующее:
with Scraper() as scraper:
# you can give him a list of words right away
for word in ['canis']:
scraper.parse_word(word)
Теперь перейдем к самому интересному. Я написал для вас функцию парсинга, насколько я понимаю ваш запрос, но данные на вашем сайте очень хаотичны. Иногда элементов, которые можно использовать для заголовков, вообще нет, иногда их слишком много, и непонятно, что использовать. Теперь логика парсера следующая:
Посмотрите, что вы получите, и скажите мне, какое имя ожидается, пока мы не берем родительские имена, только для текущего узла. Я смогу посоветовать вам еще кое-что.
def _parse_html(html):
def is_has_prev(node):
if node is None:
return False
try:
return node.get('class')[0] != table_cls_name
except (TypeError, AttributeError):
return True
soup = BeautifulSoup(html, 'lxml')
valid_tags = {'p', 'a', 'h4'} # add if there's anything else
table_cls_name = 'table-responsive'
previous_nodes = {}
tables = []
for idx, table in enumerate(soup.find_all(class_=table_cls_name)):
previous_node = table.previous_sibling
tables.append(table)
previous_nodes[idx] = []
while is_has_prev(node=previous_node):
if previous_node.name in valid_tags:
# if you need to look at nodes text
# replace the string `previous_node` with `previous_node.text`
previous_nodes[idx].append(previous_node)
previous_node = previous_node.previous_sibling
for prev_nodes, t in zip(previous_nodes.values(), tables):
print('POSSIBLES TABLE NAMES', prev_nodes)
print('RESULT TABLE', t)
print('#############################################################')
return tuple(zip(previous_nodes.values(), tables))
Вот часть результатов:
Я разработал шаблон для очистки данных. Пожалуйста, просмотрите комментарии:
<actions>
<!-- Step 1: get the html of results -->
<action_goto url = "https://outils.biblissima.fr/en/collatinus-web/" wait = "5000" />
<action_input content = "canis" enter = "true" wait = "5000">
<element loc = "#flexion_lemme" />
</action_input>
<action_setvar_element varname = "resultsHtml">
<element loc = "#results" />
<elecontent_innerhtml />
</action_setvar_element>
<!-- Step 2: parse the html of results -->
<!-- Loop 1: category, such as "ACTIF", using split/cheerio -->
<action_setvar_templstr varname = "categoryItems">
<templstr templ = "${resultsHtml}" />
<transform>
<fun_split split = "<h4>" filter = "^[A-Z]" start = "0" end = "0" resulttype = "all" insertstr = "<h4>" join = "::" />
</transform>
</action_setvar_templstr>
<action_loopinstr list = "${categoryItems}" varname = "categoryItem" split = "::">
<action_setvar_templstr varname = "categoryName">
<templstr templ = "${categoryItem}" />
<transform>
<fun_c_text loc = "h4" />
</transform>
</action_setvar_templstr>
<!-- Loop 2: subcategory, such as "Indicatif infectum", using split/cheerio -->
<action_setvar_templstr varname = "subcategoryItems">
<templstr templ = "${categoryItem}" />
<transform>
<fun_split split = "<p>" filter = "table-responsive" start = "0" end = "0" resulttype = "all" insertstr = "<p>" join = "::" />
</transform>
</action_setvar_templstr>
<action_loopinstr list = "${subcategoryItems}" varname = "subcategoryItem" split = "::">
<action_setvar_templstr varname = "subcategoryName">
<templstr templ = "${subcategoryItem}" />
<transform>
<fun_c_text loc = "p" />
</transform>
</action_setvar_templstr>
<action_setvar_templstr varname = "length">
<templstr templ = "${subcategoryItem}" />
<transform>
<fun_c_length loc = "div.table-responsive" />
<fun_concat str1 = "-1" />
<fun_tonum />
</transform>
</action_setvar_templstr>
<!-- Loop 3: table, using cheerio -->
<action_loopfor from = "0" to = "${length}" varname = "tableNo">
<action_extract tabname = "dat_00000000000012ab">
<column_templstr colname = "c01" nickname = "categoryName">
<templstr templ = "${categoryName}" />
</column_templstr>
<column_templstr colname = "c02" nickname = "subcategoryName">
<templstr templ = "${subcategoryName} #${tableNo}" />
</column_templstr>
<column_templstr colname = "c03" nickname = "table">
<templstr templ = "${subcategoryItem}" />
<transform usevar = "true">
<fun_c_html loc = "div.table-responsive" idx = "${tableNo}" />
<fun_trim />
</transform>
</column_templstr>
</action_extract>
</action_loopfor>
</action_loopinstr>
</action_loopinstr>
</actions>
Выполнив этот шаблон с использованием пакета nodejs с открытым исходным кодом, можно получить следующие данные:
вам не нужно взаимодействовать с JS, JS будет отображать HTML-страницу в соответствии с ответами HTTP-запросов. Затем вы можете получить визуализированную HTML-страницу.
Кстати, в этом случае вы также можете напрямую использовать HTML-фрагменты результатов в ответе на запрос.
Подождите, а как вам удалось получить данные, не взаимодействуя с JavaScript веб-сайта? Извините, я новичок в этой идее, которую вы здесь показали. Спасибо вам огромное, кстати.