Парсинг органических, рекламных результатов Qwant с помощью Python
?Кратко о сути: обучающий блог пост о парсинге позиции сайта, заголовка, ссылки, отображаемой ссылки, описания и иконки из поисковой выдачи qwant используя Python.
?Что понадобится: понимание циклов, структур данных, обработка исключений, и, базовое понимание CSS
селекторов. А так же bs4
, requests
, lxml
библиотеки.
⏱️Сколько времени займет: ~15-30 минут.
Что такое Qwant Search
Qwant это базирующаяся в Париже европейская поисковая система с независимой системой индексирования которая не отслеживает действия пользователей для использования этих данных в рекламных целях.
Qwant доступна на 26 языках и имеющая более 30 миллионов индивидуальных пользователей в месяц по всему миру.
Что парсим

Что необходимо для старта
Базовое понимание CSS
селекторов
CSS
селекторы объявляют к какой части разметки применяется тот или иной стиль, что позволяет извлекать данные из совпадающих тегов и атрибутов.
Если вы не парсили с помощью CSS
селекторов, есть блог пост посвященный этому на английском языке - how to use CSS selectors when web-scraping который рассказывает о том что это такое, их плюсы и минусы и для чего они вообще с точки зрения веб скрейпинга.
Отдельное вирутуальное окружение
Вкратце, это штука, которая создает независимый набор установленных библиотек включая возможность установки разных версий Python которые могут сосуществовать друг с другом одновременно на одной системе, что предотвращает конфликты библиотек и разных версий Python.
Если вы не работали с виртуальным окружением ранее, взгляните на посвященный этому блог пост на английском языке - Python virtual environments tutorial using Virtualenv and Poetry. Это не является строгой необходимостью к данному посту.
Установка библиотек:
pip install requests
pip install lxml
pip install beautifulsoup4
Предотвращение блокировок
Есть вероятность, что запрос может быть заблокирован по тем или иным причинам.
Можете взглянуть на одиннадцать вариантов предотвращения блокировок с большинства сайтов в моём отдельном блог посте на английском языке - how to reduce the chance of being blocked while web-scraping.
Процесс
Если объяснение не нужно:
забирайте код в секции весь код целиком,
Начальный код для органической и рекламной выдачи:
from bs4 import BeautifulSoup
import requests, lxml, json
headers = {
"User-Agent": "Mozilla/5.0 (Linux; Android 10; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36 EdgA/46.1.2.5140"
}
params = {
"q": "minecraft",
"t": "web"
}
html = requests.get("https://www.qwant.com/", params=params, headers=headers, timeout=20)
soup = BeautifulSoup(html.text, "lxml")
# ...
Импортируем библиотеки:
from bs4 import BeautifulSoup
import requests, lxml, json
Добавляем user-agent
и query параметры к запросу:
headers = {
"User-Agent": "Mozilla/5.0 (Linux; Android 10; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36 EdgA/46.1.2.5140"
}
params = {
"q": "minecraft", # поисковый запрос
"t": "web" # qwant параметр для отображения поисковых веб результатов
}
Отправляем запрос, добавляем timeout
аргумент, создаем BeautifulSoup()
объект:
html = requests.get("https://www.qwant.com/", params=params, headers=headers, timeout=20)
soup = BeautifulSoup(html.text, "lxml")
timeout
параметр скажетrequests
остановиться ожидать ответ на запрос после X числа секунд.BeautifulSoup()
это то что стягивает HTML.lxml
это HTML парсер.
Парсинг органических результатов
def scrape_organic_results():
organic_results_data = []
for index, result in enumerate(soup.select("[data-testid=webResult]"), start=1):
title = result.select_one(".WebResult-module__title___MOBFg").text
link = result.select_one(".Stack-module__VerticalStack___2NDle.Stack-module__Spacexxs___3wU9G a")["href"]
snippet = result.select_one(".Box-module__marginTopxxs___RMB_d").text
try:
displayed_link = result.select_one(".WebResult-module__permalink___MJGeh").text
favicon = result.select_one(".WebResult-module__iconBox___3DAv5 img")["src"]
except:
displayed_link = None
favicon = None
organic_results_data.append({
"position": index,
"title": title,
"link": link,
"displayed_link": displayed_link,
"snippet": snippet,
"favicon": favicon
})
print(json.dumps(organic_results_data, indent=2))
Создаем временный список list()
где будут храниться извлеченные данные:
organic_results_data = []
Итерируем в цикле и парсим данные:
for index, result in enumerate(soup.select("[data-testid=webResult]"), start=1):
title = result.select_one(".WebResult-module__title___MOBFg").text
link = result.select_one(".Stack-module__VerticalStack___2NDle.Stack-module__Spacexxs___3wU9G a")["href"]
snippet = result.select_one(".Box-module__marginTopxxs___RMB_d").text
try:
displayed_link = result.select_one(".WebResult-module__permalink___MJGeh").text
favicon = result.select_one(".WebResult-module__iconBox___3DAv5 img")["src"]
except:
displayed_link = None
favicon = None
Чтобы извлечь позицию сайта, мы можем использовать enumerate() функцию которая добавляет счетчик к итерируемому объекту и возвращает его, и, установить start
аргумент равным 1, для того чтобы начать отсчёт с единицы (1), а не с нуля (0).
Чтобы обработать None
значения, мы можем использовать блок try/except
, то есть если из самой выдачи Qwant ничего не будет, мы установим ту или иное значение на None
соответственно. В ином случае вылетит ошибка которая, скажет что нет того или иного элемента, атрибута в HTML дереве.
Добавляем извлеченные данные во временный список list()
как словарь dict()
:
organic_results_data.append({
"position": index,
"title": title,
"link": link,
"displayed_link": displayed_link,
"snippet": snippet,
"favicon": favicon
})
Вывод:
print(json.dumps(organic_results_data, indent=2))
# часть вывода:
'''
[
{
"position": 1,
"title": "Minecraft Official Site | Minecraft",
"link": "https://www.minecraft.net/",
"displayed_link": "minecraft.net",
"snippet": "Get all-new items in the Minecraft Master Chief Mash-Up DLC on 12/10, and the Superintendent shirt in Character Creator, free for a limited time! Learn more. Climb high and dig deep. Explore bigger mountains, caves, and biomes along with an increased world height and updated terrain generation in the Caves & Cliffs Update: Part II! Learn more . Play Minecraft games with Game Pass. Get your ...",
"favicon": "https://s.qwant.com/fav/m/i/www_minecraft_net.ico"
},
... другие результаты
{
"position": 10,
"title": "Minecraft - download free full version game for PC ...",
"link": "http://freegamepick.net/en/minecraft/",
"displayed_link": "freegamepick.net",
"snippet": "Minecraft Download Game Overview. Minecraft is a game about breaking and placing blocks. It's developed by Mojang. At first, people built structures to protect against nocturnal monsters, but as the game grew players worked together to create wonderful, imaginative things. It can als o be about adventuring with friends or watching the sun rise over a blocky ocean.",
"favicon": "https://s.qwant.com/fav/f/r/freegamepick_net.ico"
}
]
'''
Парсинг рекламных результатов
def scrape_ad_results():
ad_results_data = []
for index, ad_result in enumerate(soup.select("[data-testid=adResult]"), start=1):
ad_title = ad_result.select_one(".WebResult-module__title___MOBFg").text
ad_link = ad_result.select_one(".Stack-module__VerticalStack___2NDle a")["href"]
ad_displayed_link = ad_result.select_one(".WebResult-module__domain___1LJmo").text
ad_snippet = ad_result.select_one(".Box-module__marginTopxxs___RMB_d").text
ad_favicon = ad_result.select_one(".WebResult-module__iconBox___3DAv5 img")["src"]
ad_results_data.append({
"ad_position": index,
"ad_title": ad_title,
"ad_link": ad_link,
"ad_displayed_link": ad_displayed_link,
"ad_snippet": ad_snippet,
"ad_favicon": ad_favicon
})
print(json.dumps(ad_results_data, indent=2))
Создаём временный список list()
для хранения извлеченных данных:
ad_results_data = []
Итерируем и извлекаем:
for index, ad_result in enumerate(soup.select("[data-testid=adResult]"), start=1):
ad_title = ad_result.select_one(".WebResult-module__title___MOBFg").text
ad_link = ad_result.select_one(".Stack-module__VerticalStack___2NDle a")["href"]
ad_displayed_link = ad_result.select_one(".WebResult-module__domain___1LJmo").text
ad_snippet = ad_result.select_one(".Box-module__marginTopxxs___RMB_d").text
ad_favicon = ad_result.select_one(".WebResult-module__iconBox___3DAv5 img")["src"]
Такой же подход используется для извлечения позиции сайта. Единственное отличие в другом CSS
"контейнер" селекторе [data-testid=adResult]
, когда в органической выдаче это другой селектор [data-testid=webResult]
. Все остальное осталось прежним.
Добавляем извлеченные данные во временный список list()
как словарь:
ad_results_data.append({
"ad_position": index,
"ad_title": ad_title,
"ad_link": ad_link,
"ad_displayed_link": ad_displayed_link,
"ad_snippet": ad_snippet
})
Вывод:
print(json.dumps(ad_results_data, indent=2))
# в данном случае в выводе один рекламный результат:
'''
[
{
"ad_position": 1,
"ad_title": "Watch Movies & TV on Amazon - Download in HD on Amazon Video",
"ad_link": "https://www.bing.com/aclick?ld=e8pyYjhclU87kOyQ4ap78CRzVUCUxgK0MGMfKx1YlQe_w7Nbzamra9cSRmPFAtSOVF4MliAqbJNdotR3G-aqHSaMOI0tqV9K0EAFRTemYDKhbqLyjFW93Lsh0mnyySb8oIj6GXADnoePUk-etFDgSvPdZI0xObBo4hesqbOHypYhSGeJ-ZbG1eY0kijv95k0XJ9WKPPA&u=aHR0cHMlM2ElMmYlMmZ3d3cuYW1hem9uLmNvLnVrJTJmcyUyZiUzZmllJTNkVVRGOCUyNmtleXdvcmRzJTNkbWluZWNyYWZ0JTJidGhlJTI2aW5kZXglM2RhcHMlMjZ0YWclM2RoeWRydWtzcG0tMjElMjZyZWYlM2RwZF9zbF8ydmdscmFubWxwX2UlMjZhZGdycGlkJTNkMTE0NDU5MjQzNjk0ODQzOSUyNmh2YWRpZCUzZDcxNTM3MTUwMzgzNDA4JTI2aHZuZXR3JTNkcyUyNmh2cW10JTNkZSUyNmh2Ym10JTNkYmUlMjZodmRldiUzZG0lMjZodmxvY2ludCUzZCUyNmh2bG9jcGh5JTNkMTQxMTcxJTI2aHZ0YXJnaWQlM2Rrd2QtNzE1Mzc2Njc4MjI5NzklM2Fsb2MtMjM1JTI2aHlkYWRjciUzZDU5MTJfMTg4MTc4NQ&rlid=c61aa73b62e916116cbdc687c021190a",
"ad_displayed_link": "amazon.co.uk",
"ad_snippet": "Download now. Watch anytime on Amazon Video.",
"ad_favicon": "https://s.qwant.com/fav/a/m/www_amazon_co_uk.ico"
}
]
'''
Код целиком
from bs4 import BeautifulSoup
import requests, lxml, json
headers = {
"User-Agent": "Mozilla/5.0 (Linux; Android 10; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36 EdgA/46.1.2.5140"
}
params = {
"q": "minecraft",
"t": "web"
}
html = requests.get("https://www.qwant.com/", params=params, headers=headers, timeout=20)
soup = BeautifulSoup(html.text, "lxml")
def scrape_organic_results():
organic_results_data = []
for index, result in enumerate(soup.select("[data-testid=webResult]"), start=1):
title = result.select_one(".WebResult-module__title___MOBFg").text
link = result.select_one(".Stack-module__VerticalStack___2NDle.Stack-module__Spacexxs___3wU9G a")["href"]
snippet = result.select_one(".Box-module__marginTopxxs___RMB_d").text
try:
displayed_link = result.select_one(".WebResult-module__permalink___MJGeh").text
favicon = result.select_one(".WebResult-module__iconBox___3DAv5 img")["src"]
except:
displayed_link = None
favicon = None
organic_results_data.append({
"position": index,
"title": title,
"link": link,
"displayed_link": displayed_link,
"snippet": snippet,
"favicon": favicon
})
print(json.dumps(organic_results_data, indent=2))
def scrape_ad_results():
ad_results_data = []
for index, ad_result in enumerate(soup.select("[data-testid=adResult]"), start=1):
ad_title = ad_result.select_one(".WebResult-module__title___MOBFg").text
ad_link = ad_result.select_one(".Stack-module__VerticalStack___2NDle a")["href"]
ad_displayed_link = ad_result.select_one(".WebResult-module__domain___1LJmo").text
ad_snippet = ad_result.select_one(".Box-module__marginTopxxs___RMB_d").text
ad_favicon = ad_result.select_one(".WebResult-module__iconBox___3DAv5 img")["src"]
ad_results_data.append({
"ad_position": index,
"ad_title": ad_title,
"ad_link": ad_link,
"ad_displayed_link": ad_displayed_link,
"ad_snippet": ad_snippet,
"ad_favicon": ad_favicon
})
print(json.dumps(ad_results_data, indent=2))
Ссылки
Заключение
С вопросами и предложениями по блог посту пишите в комментариях или в Твиттер на @dimitryzub или @serp_api.
Ссылка на оригинальный блог пост - Scrape Qwant Organic and Ad Results using Python.
Присоединяйтесь к нам на Reddit | Twitter | YouTube
Добавить запрос на фичу? или существующий баг?