This commit is contained in:
Dmitry
2026-04-26 15:40:50 +03:00
parent 0b41577a42
commit 56c54f504b
6 changed files with 124 additions and 124 deletions
+21 -29
View File
@@ -41,7 +41,7 @@ def download_document_page(url: str, allow_partial: bool = False) -> str:
time.sleep(0.5)
continue
if allow_partial or "</html>" in page_html.lower():
if allow_partial or "</html>" in page_html[-100:].lower():
return page_html
last_html = page_html
@@ -65,24 +65,21 @@ def main_content(tree):
return nodes[0] if nodes else None
def article_title(page_html: str) -> str:
tree = html.fromstring(page_html)
def article_title(tree) -> str:
content = main_content(tree)
if content is None:
return ""
h1 = content.xpath(".//h1")
if h1 and clean_text(h1[0].text_content()):
return clean_text(h1[0].text_content())
if h1:
text = clean_text(h1[0].text_content())
if text:
return text
styles = content.xpath(".//div[contains(concat(' ', normalize-space(@class), ' '), ' doc-style ')]")
return clean_text(styles[0].text_content()) if styles else ""
def is_article_page(page_html: str) -> bool:
return bool(ARTICLE_RE.match(article_title(page_html)))
def service_line(line: str) -> bool:
low = line.lower()
@@ -90,7 +87,7 @@ def service_line(line: str) -> bool:
return True
if low.startswith("(част") and ("в ред." in low or "введен" in low):
return True
if "федеральн" in low and any(word in low for word in ("ред.", "введен", "утратил", "утратила")):
if "федеральн" in low and any(word in low for word in ("ред.", "введен", "утратил")):
return True
if low in {"президент", "российской федерации", "б.ельцин", "москва, кремль"}:
return True
@@ -102,8 +99,7 @@ def service_line(line: str) -> bool:
return False
def extract_page_text(page_html: str, url: str = "") -> str:
tree = html.fromstring(page_html)
def extract_page_text(tree) -> str:
content = main_content(tree)
if content is None:
return ""
@@ -125,8 +121,7 @@ def extract_page_text(page_html: str, url: str = "") -> str:
return "\n".join(lines)
def extract_article_urls(page_html: str, base_url: str) -> list[str]:
tree = html.fromstring(page_html)
def extract_article_urls(tree, base_url: str) -> list[str]:
prefix = document_prefix(base_url)
result = []
@@ -139,8 +134,7 @@ def extract_article_urls(page_html: str, base_url: str) -> list[str]:
return result
def extract_next_document_url(page_html: str, base_url: str) -> str | None:
tree = html.fromstring(page_html)
def extract_next_document_url(tree, base_url: str) -> str | None:
prefix = document_prefix(base_url)
links = tree.xpath("//a[contains(concat(' ', normalize-space(@class), ' '), ' pages__right ')][@href]")
if not links:
@@ -175,7 +169,7 @@ def crawl_document(start_url: str = START_URL, max_pages: int = 800) -> list[dic
try:
start_html = download_document_page(start_url, allow_partial=True)
add_known_urls(known_urls, extract_article_urls(start_html, start_url))
add_known_urls(known_urls, extract_article_urls(html.fromstring(start_html), start_url))
current_url = known_urls[0] if known_urls else FIRST_ARTICLE_URL
except RuntimeError as exc:
print(f"[crawler] start page is unavailable: {exc}")
@@ -191,23 +185,21 @@ def crawl_document(start_url: str = START_URL, max_pages: int = 800) -> list[dic
print(f"[crawler] skip page {current_url}: {exc}")
break
add_known_urls(known_urls, extract_article_urls(page_html, current_url))
tree = html.fromstring(page_html)
title = article_title(tree)
article_urls = extract_article_urls(tree, current_url)
next_url = extract_next_document_url(tree, current_url)
if is_article_page(page_html):
text = extract_page_text(page_html, current_url)
add_known_urls(known_urls, article_urls)
if ARTICLE_RE.match(title):
text = extract_page_text(tree)
if text:
pages.append(
{
"url": current_url,
"title": article_title(page_html),
"text": text,
}
)
pages.append({"url": current_url, "title": title, "text": text})
if "Статья 361." in article_title(page_html):
if "Статья 361." in title:
break
next_url = extract_next_document_url(page_html, current_url)
if next_url is None:
next_url = next_known_article(current_url, known_urls, visited)
current_url = next_url
+11 -9
View File
@@ -85,25 +85,27 @@ else:
MORPH = pymorphy3.MorphAnalyzer()
def normalize(word: str) -> str:
return word.lower().replace("ё", "е")
@lru_cache(maxsize=100_000)
def parse_word(word: str):
w = word.lower().replace("ё", "е")
if MORPH is None:
return normalize(word), ""
return w, ""
parsed = MORPH.parse(normalize(word))
parsed = MORPH.parse(w)
if not parsed:
return normalize(word), ""
return w, ""
best = parsed[0]
return normalize(best.normal_form), str(best.tag)
return best.normal_form.lower().replace("ё", "е"), str(best.tag)
@lru_cache(maxsize=10_000)
def _tag_parts(tag: str) -> frozenset[str]:
return frozenset(re.split(r"[, ]+", tag))
def has_tag(tag: str, names: set[str]) -> bool:
return bool(set(re.split(r"[, ]+", tag)) & names)
return bool(_tag_parts(tag) & names)
def is_numeral(lemma: str, tag: str) -> bool: