2026, Jan 09 18:02
Парсинг Google в Selenium: исправляем CSS‑селектор и настраиваем ожидания
Почему Selenium возвращает пустую выдачу Google: исправляем CSS‑селектор, добавляем явные ожидания и user‑agent, проверяем разметку и headless‑режим.
Сбор ссылок из результатов Google, чтобы получить сайты школ и адреса электронной почты, часто кажется простой задачей, пока скрипт вдруг не возвращает пустой список без явной ошибки. Ниже — точный разбор одной из таких ловушек в Selenium и способ её устранить без переделки общей логики парсинга.
Постановка задачи
Нужно выполнить запрос к Google по страницам типа PSHE на доменах .sch.uk и извлечь URL из выдачи. Изначальный скрипт запускает безголовый Chrome, формирует URL поиска и находит ссылки в контейнере результатов. Но на выходе — пусто.
Воспроизводимый код, который не работает
Фрагмент ниже повторяет ключевое поведение исходного подхода, сохраняя логику программы и лишь меняя имена:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.options import Options
import time
def crawl_google(q, limit=10):
opts = Options()
opts.add_argument("--headless") # Запуск безголового браузера
opts.add_argument("--disable-blink-features=AutomationControlled")
opts.add_argument("--no-sandbox")
opts.add_argument("--disable-dev-shm-usage")
browser = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=opts)
target = f"https://www.google.com/search?q={q}&num={limit}"
browser.get(target)
time.sleep(2)
collected = []
nodes = browser.find_elements(By.CSS_SELECTOR, 'div.yuRUbf > a')
for node in nodes:
href = node.get_attribute('href')
if href:
collected.append(href)
browser.quit()
return collected
phrase = "PSHE site:.sch.uk"
out = crawl_google(phrase, limit=20)
for idx, link in enumerate(out, 1):
print(f"{idx}. {link}")
Что на самом деле идёт не так
Локатор использует комбинатор прямого потомка в CSS‑селекторе. Выражение div.yuRUbf > a ищет тег <a>, который является непосредственным ребёнком указанного <div>. На странице выдачи ссылка находится не напрямую внутри, а глубже по дереву. Из‑за отсутствия отношения родитель → прямой потомок выборка ничего не находит, и скрипт молча возвращает пустой список.
Есть и практическая тонкость. Имена классов вроде yuRUbf запутаны и могут меняться, поэтому полагаться на них рискованно. Кроме того, после первого перехода можно упереться в reCaptcha, и тогда ожидаемая разметка просто не загрузится. В таких случаях полезно проверить page_source сразу после навигации или на время отключить безголовой режим — так видно, что именно получает браузер.
Решение: поправить селектор
Переход от комбинатора прямого потомка к комбинатору потомка решает проблему. Селектор div.yuRUbf a находит ссылки, вложенные на любой глубине внутри указанного контейнера.
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.options import Options
import time
def crawl_google(q, limit=10):
opts = Options()
opts.add_argument("--headless")
opts.add_argument("--disable-blink-features=AutomationControlled")
opts.add_argument("--no-sandbox")
opts.add_argument("--disable-dev-shm-usage")
browser = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=opts)
target = f"https://www.google.com/search?q={q}&num={limit}"
browser.get(target)
time.sleep(2)
collected = []
nodes = browser.find_elements(By.CSS_SELECTOR, 'div.yuRUbf a')
for node in nodes:
href = node.get_attribute('href')
if href:
collected.append(href)
browser.quit()
return collected
phrase = "PSHE site:.sch.uk"
out = crawl_google(phrase, limit=20)
for idx, link in enumerate(out, 1):
print(f"{idx}. {link}")
Более надёжный вариант той же логики
Стабильность повышают замена произвольных задержек на явные ожидания, отказ от лишней зависимости в пользу Selenium Manager и передача строки user‑agent. Вариант ниже сохраняет то же поведение, но включает эти правки и исправленный селектор:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
def fetch_serp(q, limit=10):
chrome_opts = Options()
chrome_opts.add_argument("--headless")
chrome_opts.add_argument("--disable-blink-features=AutomationControlled")
chrome_opts.add_argument("--no-sandbox")
chrome_opts.add_argument("--disable-dev-shm-usage")
chrome_opts.add_argument(
"user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
)
browser = webdriver.Chrome(options=chrome_opts)
page = f"https://www.google.com/search?q={q}&num={limit}"
browser.get(page)
browser.maximize_window()
wait_for = WebDriverWait(browser, 10)
hrefs = []
elements = wait_for.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'div.yuRUbf a')))
for el in elements:
href = el.get_attribute('href')
if href:
hrefs.append(href)
browser.quit()
return hrefs
term = "PSHE site:.sch.uk"
serp = fetch_serp(term, limit=20)
for idx, u in enumerate(serp, 1):
print(f"{idx}. {u}")
Почему это важно
Разница между комбинатором прямого потомка и потомка в записи минимальна, но по эффекту — критична. Одна неверная буква способна обрушить весь шаг извлечения без каких‑либо исключений. Стоит привести локатор в соответствие с реальной структурой DOM — и процесс сразу оживает. Дополнительно: замена фиксированных пауз на ожидания делает прогоны предсказуемее, встроенный Selenium Manager избавляет от лишнего менеджера драйверов, а указание user‑agent помогает там, где цель отвергает стандартную «автоматическую» сигнатуру. При парсинге выдачи не забывайте и о защитных механизмах: разметка может не подгружаться как задумано, поэтому просмотр полученного HTML или временное отключение headless‑режима — практичный способ проверить, что реально видит браузер.
Итоги
Если ваш скрипт для Google возвращает пустой список, в первую очередь проверьте семантику селектора. Когда ссылка не является прямым ребёнком, используйте селектор потомка. Отдавайте предпочтение явным ожиданиям, упростите настройку драйвера встроенным менеджером и передавайте правдоподобный user‑agent. Осторожнее с запутанными именами классов и помните: защитные страницы могут менять то, что получает автоматизация. Часто этих небольших, точечных правок достаточно, чтобы превратить пустую выборку в рабочий парсинг без переделки процесса.