selenium / beautiful soup 中按网站顺序排列的 HTML 元素数组列表

问题描述 投票:0回答:1

我正在用 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”。

  • 最后一个问题是,有些标题内部包含两个或三个(?)表,所以我希望可以将它们标记为“subjonctif #1”和“subjonctif #2”。

在我看来(如果我错了,请纠正我)如果程序知道每个表之前的内容,所有这些问题都可以轻松解决。

我还没有真正尝试过任何东西,因为我不确定从哪里开始。

如果有人可以提供帮助,我将不胜感激。

python html selenium-webdriver web-scraping
1个回答
0
投票

我要建议你的第一件事就是放弃

selenium
,你不需要在这里使用它。我编写了一个简单的
spider
,它没有
selenium
,只是解析
token
向此
endpoint
请求所需的 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.start_url = 'https://outils.biblissima.fr/en/collatinus-web/'
        self.session = None
        self.form_data = None
        self.form_option = form_option

    def __enter__(self):
        self.session = requests.session()
        response = self.session.get(self.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']

        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 ValueError('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)

您可以在此处找到表单的令牌: screen

要工作,您将需要以下库:

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 and previous_node.text:
                previous_nodes[idx].append(previous_node.text)
            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))

以下是部分结果: screen2

© www.soinside.com 2019 - 2024. All rights reserved.