Списки массивов HTML-элементов, упорядоченные по веб-сайтам в селене/красивом супе

Я пишу скрипт на 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».

На мой взгляд (поправьте меня, если я ошибаюсь) все эти проблемы можно было бы легко исправить, если бы программа знала, что находится перед каждой таблицей.

Я особо ничего не пробовал, так как не знаю, с чего начать.

Если бы кто-нибудь мог помочь, это было бы очень признательно.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
0
97
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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)

Теперь перейдем к самому интересному. Я написал для вас функцию парсинга, насколько я понимаю ваш запрос, но данные на вашем сайте очень хаотичны. Иногда элементов, которые можно использовать для заголовков, вообще нет, иногда их слишком много, и непонятно, что использовать. Теперь логика парсера следующая:

  1. Найдите все таблицы.
  2. Для каждой таблицы — найти все предыдущие элементы до предыдущей таблицы. Соберите все это в список.

Посмотрите, что вы получите, и скажите мне, какое имя ожидается, пока мы не берем родительские имена, только для текущего узла. Я смогу посоветовать вам еще кое-что.

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 = "&lt;h4&gt;" filter = "^[A-Z]" start = "0" end = "0" resulttype = "all" insertstr = "&lt;h4&gt;" 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 = "&lt;p&gt;" filter = "table-responsive" start = "0" end = "0" resulttype = "all" insertstr = "&lt;p&gt;" 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 с открытым исходным кодом, можно получить следующие данные:

Подождите, а как вам удалось получить данные, не взаимодействуя с JavaScript веб-сайта? Извините, я новичок в этой идее, которую вы здесь показали. Спасибо вам огромное, кстати.

Mufarrid Ansari 11.07.2024 02:54

вам не нужно взаимодействовать с JS, JS будет отображать HTML-страницу в соответствии с ответами HTTP-запросов. Затем вы можете получить визуализированную HTML-страницу.

LetsScrapeData 11.07.2024 05:38

Кстати, в этом случае вы также можете напрямую использовать HTML-фрагменты результатов в ответе на запрос.

LetsScrapeData 11.07.2024 05:40

Другие вопросы по теме