Pull to refresh

Парсинг органических, рекламных результатов Qwant с помощью Python

Original author: Dmitry Zub

?Кратко о сути: обучающий блог пост о парсинге позиции сайта, заголовка, ссылки, отображаемой ссылки, описания и иконки из поисковой выдачи 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

Добавить запрос на фичу? или существующий баг?

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.