我正在用 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”。
在我看来(如果我错了,请纠正我)如果程序知道每个表之前的内容,所有这些问题都可以轻松解决。
我还没有真正尝试过任何东西,因为我不确定从哪里开始。
如果有人可以提供帮助,我将不胜感激。
我要建议你的第一件事就是放弃
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)
要工作,您将需要以下库:
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 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))