2025, Oct 19 21:17

Как скачать PDF Gazzetta Ufficiale за 1969 год с Selenium

Разбираем, почему ссылки на PDF Gazzetta Ufficiale 1969 не работают, и показываем способ скачивания через Selenium: выбор года и автоматическая загрузка.

Скачивание исторических PDF из итальянской Gazzetta Ufficiale – Serie Generale за 1969 год выглядит обманчиво простым: есть публичный архив, отдельные страницы выпусков и знакомая конечная точка загрузки. На практике статические подходы часто вовсе не показывают ссылок, а вручную составленные URL для скачивания возвращают HTML-страницы с ошибками вместо бинарных PDF. Ниже — краткий разбор, почему это происходит, и как надежно автоматизировать загрузки с Selenium.

Краткий обзор проблемы

Прямые HTTP‑запросы к индексам архива за 1969 год работают нормально, и страницы конкретных выпусков легко перечислить. Трудности начинаются, когда нужно извлечь реальную ссылку на «pubblicazione completa non certificata». Во многих живых сессиях ожидаемые элементы, такие как a.download_pdf, отсутствуют в HTML, отданном сервером, хотя в сохраненных копиях они могут встречаться. Попытка сконструировать URL /do/gazzetta/downloadPdf по дате и номеру выпуска приводит к HTML с сообщением «Il pdf selezionato non è stato trovato» вместо бинарного файла PDF. Навигация Selenium к селектору года тоже получается хрупкой при наивном выборе элементов: нужная опция может быть скрыта, находиться во фрейме или перекрываться оверлеями. Зато управление интерфейсом с клавиатуры, а затем разбор итогового исходника страницы стабильно открывают настоящие ссылки на загрузку.

Минимальный неудачный подход (requests + BeautifulSoup)

Ниже — пример, который собирает все страницы выпусков за 1969 год и пытается найти рабочую ссылку на PDF на одной из них. Он воспроизводит описанное поведение: страницы выпуска находятся, но в живом DOM ссылки на скачивание нет.

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse, parse_qs

ORIGIN = "https://www.gazzettaufficiale.it"
TARGET_YEAR = 1969
YEAR_INDEX = f"{ORIGIN}/ricercaArchivioCompleto/serie_generale/{TARGET_YEAR}"

http = requests.Session()
http.headers.update({
    "User-Agent": "Mozilla/5.0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Referer": ORIGIN,
})

# 1) Collect detail pages (date + issue number)
resp = http.get(YEAR_INDEX, timeout=60)
resp.raise_for_status()
dom = BeautifulSoup(resp.text, "html.parser")
detail_pages = []
for node in dom.find_all("a", href=True):
    link = node["href"]
    if ("/gazzetta/serie_generale/caricaDettaglio" in link
        and "dataPubblicazioneGazzetta=" in link
        and "numeroGazzetta=" in link):
        detail_pages.append(urljoin(ORIGIN, link))

print("Detail pages found:", len(detail_pages))
print("Sample:", detail_pages[:3])

# 2) For one detail page, try to discover a real "download PDF" link
detail_url = detail_pages[0]
resp = http.get(detail_url, timeout=60, headers={"Referer": YEAR_INDEX})
resp.raise_for_status()
dom = BeautifulSoup(resp.text, "html.parser")

# Try common selectors / texts
download_anchor = (dom.select_one('a.download_pdf[href]')
                   or dom.select_one('a[href*="/do/gazzetta/downloadPdf"]'))
if not download_anchor:
    for node in dom.find_all("a", href=True):
        if "scarica il pdf" in (node.get_text() or "").lower():
            download_anchor = node
            break

print("Download link found on detail page?", bool(download_anchor))
if download_anchor:
    print("Download href:", urljoin(ORIGIN, download_anchor["href"]))

Этот код дает полный список страниц выпусков, но стабильно не находит ссылку на скачивание для 1969 года. Ручная сборка URL для этих выпусков возвращает HTML с сообщением «not found», а не сам PDF.

Почему так происходит

На исторических страницах нужные ссылки на скачивание не всегда присутствуют в HTML, который видит парсер на базе requests. Элементы интерфейса вроде выбора года могут быть скрыты, помещены во фреймы или перекрыты. В отдельных сессиях ожидаемые ссылки download_pdf так и не появляются. Ручная генерация адреса downloadPdf приводит к ответам вроде «Il pdf selezionato non è stato trovato», то есть сервер отвергает запрос в таком виде. В этой ситуации уместен браузерный подход: он позволяет сайту выполнить клиентскую логику и установить ровно тот контекст сессии, которого ждет сервер. Как отмечают практики, это может быть необходимо, когда сервер зависит от cookies, заголовков или структура страниц меняется внутри архива.

Рабочее решение на Selenium

Скрипт ниже открывает официальный поиск «Formato grafico PDF», выбирает год через клавиатурную навигацию, отправляет форму, разбирает получившуюся страницу для извлечения реальных ссылок a.download_pdf, а затем запрашивает каждый URL в той же браузерной сессии. Укажите save_dir — каталог для сохранения PDF. Режим headless включен; ожидание завершения каждой загрузки реализовано фиксированным сном, при желании можно контролировать прогресс по временным файлам с расширением .crdownload.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
import time
from lxml import html

SEARCH_URL = "https://www.gazzettaufficiale.it/ricerca/pdf/foglio_ordinario2/2/0/0?reset=true"
save_dir = "/home/lmc/tmp/test-ws/gaz"

chrome_cfg = webdriver.ChromeOptions()
chrome_cfg.add_argument("start-maximized")
chrome_cfg.add_argument("window-size=2880x1620")
chrome_cfg.add_argument("--headless")
chrome_cfg.set_capability("pageLoadStrategy", "normal")
chrome_cfg.add_argument("--enable-javascript")

cfg_prefs = {
    "profile.managed_default_content_settings.images": 2,
    "permissions.default.stylesheet": 2,
    "download.default_directory": save_dir,
    "download.prompt_for_download": False,
    "download.directory_upgrade": True,
}
chrome_cfg.add_experimental_option("prefs", cfg_prefs)

browser = webdriver.Chrome(options=chrome_cfg)
browser.implicitly_wait(30)
browser.get(SEARCH_URL)

try:
    year_select = browser.find_element(By.ID, 'annoPubblicazione')
    year_select.click()
    time.sleep(2)

    chain = ActionChains(browser)
    for _ in range(17):
        chain.send_keys(Keys.ARROW_DOWN)
    chain.send_keys(Keys.ENTER)
    chain.perform()

    search_btn = browser.find_element(By.XPATH, '//input[@name="cerca"]')
    search_btn.click()

    time.sleep(2)
    markup = browser.page_source

    tree = html.fromstring(markup)
    links = tree.xpath('//a[@class="download_pdf"]/@href')
    print(f"first link: {links[0] if links else 'none'}")
    print(f"total links: {len(links)}")

    for path in links:
        pdf_url = f"https://www.gazzettaufficiale.it{str(path)}"
        print(f"Downloading: {pdf_url}")
        browser.get(pdf_url)
        time.sleep(8)

except Exception as exc:
    print("Unexpected error during processing")
    raise exc
finally:
    browser.quit()

Если на вашей машине загрузки выглядят незавершенными, используйте простой помощник для ожидания окончания записи Chrome: он проверяет наличие временных расширений. Ниже — вариант с тайм‑аутом и настраиваемым интервалом опроса.

def monitor_downloads(dir_path, timeout=60, poll_each=1):
    import glob
    start = time.time()
    while glob.glob(f"{dir_path}/*.crdownload") and time.time() - start < timeout:
        time.sleep(poll_each)
    print(f"Download complete in {time.time() - start:.2f} seconds.")

Зачем это важно

Попытки напрямую обращаться к конечной точке загрузки без нужного контекста сессии тихо возвращают HTML‑страницы с ошибками. Если их сохранить с расширением .pdf, получится сотня «битых» файлов. Управляемый браузером сценарий избегает этой ловушки: он повторяет действия реального пользователя — выбор года в официальном интерфейсе, ожидание формирования результатов и переход по ссылкам, сгенерированным именно для текущей сессии.

Практические выводы

Если в представлении архива обычный HTTP‑клиент не видит анкоров download_pdf, не тратьте время на обратную разработку URL, которые отвечают «Il pdf selezionato non è stato trovato». Предпочитайте рабочий процесс на Selenium: он использует интерфейс сайта, чтобы показать валидные ссылки, и сохраняет те cookies и заголовки, которые ожидает сервер. Когда прямой выбор опции в выпадающем списке ведет себя нестабильно, используйте клавиши навигации и Enter для подтверждения. Подстройте стратегию ожидания загрузок под свою среду — увеличьте паузу или опрашивайте временные файлы, пока браузер не завершит запись.

С таким подходом можно воспроизводимо получать все PDF «pubblicazione completa non certificata» за 1969 год для выпусков Serie Generale, в том числе в headless‑режиме и без ручных действий.

Статья основана на вопросе на StackOverflow от Mark и ответе от LMC.