- Что будет соскабливать
- Полный код
- "Подготовка"
- Пояснение кода
- Кодовая среда верхнего уровня
- Прокрутить страницу
- Очистить события Google
- Использование Google Events API от SerpApi
- Ссылки
Что будет очищено
Полный код
Если вам не нужны объяснения, посмотрите полный пример кода в онлайн-IDE.
import time, json from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from parsel import Selector def scroll_page(url): service = Service(ChromeDriverManager().install()) options = webdriver.ChromeOptions() options.add_argument('--headless') options.add_argument('--lang=en') options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36') options.add_argument('--no-sandbox') driver = webdriver.Chrome(service=service, options=options) driver.get(url) old_height = driver.execute_script(""" function getHeight() { return document.querySelector('.UbEfxe').scrollHeight; } return getHeight(); """) while True: driver.execute_script('document.querySelector(".UbEfxe").scrollTo(0, document.querySelector(".UbEfxe").scrollHeight);') time.sleep(1) new_height = driver.execute_script(""" function getHeight() { return document.querySelector('.UbEfxe').scrollHeight; } return getHeight(); """) if new_height == old_height: break old_height = new_height selector = Selector(driver.page_source) driver.quit() return selector def scrape_google_events(selector): data = [] for event in selector.css('.scm-c'): title = event.css('.dEuIWb::text').get() date_start = f"{event.css('.FTUoSb::text').get()} {event.css('.omoMNe::text').get()}" date_when = event.css('.Gkoz3::text').get() address = [part.css('::text').get() for part in event.css('.ov85De span')] link = event.css('.zTH3xc::attr(href)').get() location_image = 'https://www.google.com' + event.css('.lu_vs::attr(data-bsrc)').get() location_link = 'https://www.google.com' + event.css('.ozQmAd::attr(data-url)').get() description = event.css('.PVlUWc::text').get() ticket_info = [ { 'source': ticket.css('::attr(data-domain)').get(), 'link': ticket.css('.SKIyM::attr(href)').get(), 'link_type': ticket.css('.uaYYHd::text').get(), } for ticket in event.css('.RLN0we[jsname="CzizI"] div[data-domain]') ] venue_name = event.css('.RVclrc::text').get() venue_rating = float(event.css('.UIHjI::text').get()) if event.css('.UIHjI::text').get() else None venue_reviews = int(event.css('.z5jxId::text').get().replace(',', '').split()[0]) if event.css('.z5jxId::text').get() else None venue_link = 'https://www.google.com' + event.css('.pzNwRe a::attr(href)').get() if event.css('.pzNwRe a::attr(href)').get() else None data.append({ 'title': title, 'date':{ 'start_date': date_start, 'when': date_when }, 'address': address, 'link': link, 'event_location_map': { 'image': location_image, 'link': location_link }, 'description': description, 'ticket_info': ticket_info, 'venue': { 'name': venue_name, 'rating': venue_rating, 'reviews': venue_reviews, 'link': venue_link } }) return data def main(): params = { 'q': 'Events in Austin', # search query 'ibp': 'htl;events', # Google Events page 'hl': 'en', # language 'gl': 'US' # country of the search } URL = f'https://www.google.com/search?q={params["q"]}&ibp={params["ibp"]}&hl={params["hl"]}&gl={params["gl"]}' result = scroll_page(URL) google_events = scrape_google_events(result) print(json.dumps(google_events, indent=2, ensure_ascii=False)) if __name__ == '__main__': main()
Подготовка
Установите библиотеки:
pip install parsel selenium webdriver webdriver_manager
Уменьшить вероятность блокировки
Убедитесь, что вы используете заголовки запроса user-agent
, чтобы действовать как настоящий визит пользователя. Потому что по умолчанию requests
user-agent
равно python-requests
, и веб-сайты понимают, что это, скорее всего, скрипт, который отправляет запрос. Проверь, какой у тебя user-agent
.
Есть как уменьшить вероятность блокировки при парсинге поста в блоге, который может познакомить вас с базовыми и более продвинутыми подходами.
Код Пояснение
Импортировать библиотеки:
import time, json from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from parsel import Selector
json
для преобразования извлеченных данных в объект JSON.time
для работы со временем в Python.webdriver
для управления браузером изначально, как пользователь, либо локально, либо на удаленном компьютере с помощью сервера Selenium.Service
для управления запуском и остановкой ChromeDriver.Selector
XML/HTML парсер с полной поддержкой селекторов XPath и CSS.
Среда кода верхнего уровня
В начале функции определяются параметры для генерации файла URL
. Если вы хотите передать URL-адресу другие параметры, вы можете сделать это с помощью словаря params
.
Далее URL-адрес передается функции scroll_page(URL)
для прокрутки страницы и получения всех данных. Результат, возвращаемый этой функцией, передается функции scrape_google_events(result)
для извлечения необходимых данных. Объяснение этих функций будет в соответствующих заголовках ниже.
В этом коде используется общепринятое правило использования конструкции __name__ == "__main__"
:
def main(): params = { 'q': 'Events in Austin', # search query 'ibp': 'htl;events', # Google Events page 'hl': 'en', # language 'gl': 'US' # country of the search } URL = f'https://www.google.com/search?q={params["q"]}&ibp={params["ibp"]}&hl={params["hl"]}&gl={params["gl"]}' result = scroll_page(URL) google_events = scrape_google_events(result) print(json.dumps(google_events, indent=2, ensure_ascii=False)) if __name__ == '__main__': main()
Эта проверка будет выполнена только в том случае, если пользователь запустил этот файл. Если пользователь импортирует этот файл в другой, то проверка не сработает.
Вы можете посмотреть видео Python Tutorial: if __name__ == ‘__main__ для более подробной информации.
Прокрутить страницу
Функция принимает URL-адрес и возвращает полную структуру HTML.
Для начала разберемся, как работает пагинация на странице Google Events. Данные загружаются не сразу. Если пользователю нужно больше данных, он просто прокрутит раздел, где представлен список событий, и загрузит с сайта небольшой пакет данных. Соответственно, чтобы получить все данные, нужно пролистать список событий до конца.
В данном случае используется selenium
библиотека, позволяющая имитировать действия пользователя в браузере. Для работы selenium
необходимо использовать ChromeDriver
, который можно загрузить вручную или с помощью кода. В нашем случае используется второй способ. Чтобы управлять запуском и остановкой ChromeDriver
, вам нужно использовать Service
, который установит исполняемые файлы браузера под капотом:
service = Service(ChromeDriverManager().install())
Вы также должны добавить options
для правильной работы:
options = webdriver.ChromeOptions() options.add_argument('--headless') options.add_argument('--lang=en') options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36') options.add_argument('--no-sandbox')
--headless
для запуска Chrome в автономном режиме.--lang=en
чтобы установить английский язык в браузере.user-agent
действовать как настоящий пользовательский запрос от браузера, передавая его в заголовки запроса. Проверь, какой у тебяuser-agent
.--no-sandbox
чтобы chromedriver правильно работал на разных машинах.
Теперь мы можем запустить webdriver
и передать URL методу get()
.
driver = webdriver.Chrome(service=service, options=options) driver.get(url)
Алгоритм прокрутки страницы выглядит так:
- Узнайте начальную высоту страницы и запишите результат в переменную
old_height
. - Прокрутите страницу с помощью скрипта и подождите 1 секунду, пока данные загрузятся.
- Узнайте новую высоту страницы и запишите результат в переменную
new_height
. - Если переменные
new_height
иold_height
равны, то завершаем алгоритм, иначе записываем значение переменнойnew_height
в переменнуюold_height
и возвращаемся к шагу 2.
Получение высоты страницы и прокрутки выполняется путем вставки кода JavaScript в метод execute_script()
.
old_height = driver.execute_script(""" function getHeight() { return document.querySelector('.UbEfxe').scrollHeight; } return getHeight(); """) while True: driver.execute_script('document.querySelector(".UbEfxe").scrollTo(0, document.querySelector(".UbEfxe").scrollHeight);') time.sleep(1) new_height = driver.execute_script(""" function getHeight() { return document.querySelector('.UbEfxe').scrollHeight; } return getHeight(); """) if new_height == old_height: break old_height = new_height
Теперь нам нужно обработать HTML из пакета Parsel
, в который мы передаем структуру HTML
со всеми данными, полученными после прокрутки страницы. Это необходимо для успешного извлечения данных в следующей функции. После выполнения всех операций остановите драйвер:
selector = Selector(driver.page_source) driver.quit()
Функция выглядит следующим образом:
def scroll_page(url): service = Service(ChromeDriverManager().install()) options = webdriver.ChromeOptions() options.add_argument('--headless') options.add_argument('--lang=en') options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36') options.add_argument('--no-sandbox') driver = webdriver.Chrome(service=service, options=options) driver.get(url) old_height = driver.execute_script(""" function getHeight() { return document.querySelector('.UbEfxe').scrollHeight; } return getHeight(); """) while True: driver.execute_script('document.querySelector(".UbEfxe").scrollTo(0, document.querySelector(".UbEfxe").scrollHeight);') time.sleep(1) new_height = driver.execute_script(""" function getHeight() { return document.querySelector('.UbEfxe').scrollHeight; } return getHeight(); """) if new_height == old_height: break old_height = new_height selector = Selector(driver.page_source) driver.quit() return selector
В GIF ниже я демонстрирую, как работает эта функция:
Собрать события Google
Эта функция принимает полную структуру HTML и возвращает список с извлеченными данными.
Объявление списка data
, куда будут добавлены извлеченные данные:
data = []
Чтобы извлечь нужные данные, нужно найти селектор, где они находятся. В нашем случае это селектор .scm-c
, в котором собраны все события. Вам нужно повторить каждое событие в цикле:
for event in selector.css('.scm-c'): # data extraction will be here
Такие данные, как title
, date_when
, link
, description
и venue_name
, легко извлекаются:
title = event.css('.dEuIWb::text').get() date_when = event.css('.Gkoz3::text').get() link = event.css('.zTH3xc::attr(href)').get() description = event.css('.PVlUWc::text').get() venue_name = event.css('.RVclrc::text').get()
При извлечении location_image
и location_link
в результате возвращается только часть ссылки. Итак, вам нужно добавить https://www.google.com
, чтобы сформировать полную ссылку:
location_image = 'https://www.google.com' + event.css('.lu_vs::attr(data-bsrc)').get() location_link = 'https://www.google.com' + event.css('.ozQmAd::attr(data-url)').get()
Извлечение date_start
отличается от предыдущих тем, что нужно извлекать день и месяц отдельно:
date_start = f"{event.css('.FTUoSb::text').get()} {event.css('.omoMNe::text').get()}"
Списки address
и ticket_info
содержат несколько элементов в своем селекторе, поэтому они извлекаются с использованием понимания списков. Хочу обратить ваше внимание, что в ticket_info
добавлены словари со всеми необходимыми данными:
address = [part.css('::text').get() for part in event.css('.ov85De span')] ticket_info = [ { 'source': ticket.css('::attr(data-domain)').get(), 'link': ticket.css('.SKIyM::attr(href)').get(), 'link_type': ticket.css('.uaYYHd::text').get(), } for ticket in event.css('.RLN0we[jsname="CzizI"] div[data-domain]') ]
Такие данные, как venue_rating
, venue_reviews
и venue_link
, могут присутствовать не во всех событиях. Поэтому при их извлечении использовался тернарный оператор:
venue_rating = float(event.css('.UIHjI::text').get()) if event.css('.UIHjI::text').get() else None venue_reviews = int(event.css('.z5jxId::text').get().replace(',', '').split()[0]) if event.css('.z5jxId::text').get() else None venue_link = 'https://www.google.com' + event.css('.pzNwRe a::attr(href)').get() if event.css('.pzNwRe a::attr(href)').get() else None
После извлечения всех данных формируется словарь, который впоследствии добавляется в список data
:
data.append({ 'title': title, 'date':{ 'start_date': date_start, 'when': date_when }, 'address': address, 'link': link, 'event_location_map': { 'image': location_image, 'link': location_link }, 'description': description, 'ticket_info': ticket_info, 'venue': { 'name': venue_name, 'rating': venue_rating, 'reviews': venue_reviews, 'link': venue_link } })
Полная функция очистки всех данных будет выглядеть так:
def scrape_google_events(selector): data = [] for event in selector.css('.scm-c'): title = event.css('.dEuIWb::text').get() date_start = f"{event.css('.FTUoSb::text').get()} {event.css('.omoMNe::text').get()}" date_when = event.css('.Gkoz3::text').get() address = [part.css('::text').get() for part in event.css('.ov85De span')] link = event.css('.zTH3xc::attr(href)').get() location_image = 'https://www.google.com' + event.css('.lu_vs::attr(data-bsrc)').get() location_link = 'https://www.google.com' + event.css('.ozQmAd::attr(data-url)').get() description = event.css('.PVlUWc::text').get() ticket_info = [ { 'source': ticket.css('::attr(data-domain)').get(), 'link': ticket.css('.SKIyM::attr(href)').get(), 'link_type': ticket.css('.uaYYHd::text').get(), } for ticket in event.css('.RLN0we[jsname="CzizI"] div[data-domain]') ] venue_name = event.css('.RVclrc::text').get() venue_rating = float(event.css('.UIHjI::text').get()) if event.css('.UIHjI::text').get() else None venue_reviews = int(event.css('.z5jxId::text').get().replace(',', '').split()[0]) if event.css('.z5jxId::text').get() else None venue_link = 'https://www.google.com' + event.css('.pzNwRe a::attr(href)').get() if event.css('.pzNwRe a::attr(href)').get() else None data.append({ 'title': title, 'date':{ 'start_date': date_start, 'when': date_when }, 'address': address, 'link': link, 'event_location_map': { 'image': location_image, 'link': location_link }, 'description': description, 'ticket_info': ticket_info, 'venue': { 'name': venue_name, 'rating': venue_rating, 'reviews': venue_reviews, 'link': venue_link } }) return data
css()
для доступа к элементам с помощью переданного селектора.::text
или::attr(<attribute>)
для извлечения текстовых или атрибутивных данных из узла.get()
для фактического извлечения текстовых данных.
Выход:
[ { "title": "Lit @ Haute Spot", "date": { "start_date": "8 Nov", "when": "Tue, Nov 8, 5 – 10 PM CST" }, "address": [ "Haute Spot Event Venue", "1501 E New Hope Dr, Cedar Park, TX" ], "link": "https://m.facebook.com/events/haute-spot/hoobastank-lit-tried-true-tour-w-alien-ant-farm-kris-roe-of-the-ataris-at-haute-/3231075087211137/", "event_location_map": { "image": "https://www.google.com/maps/vt/data=Mi6idsSxpVUEprl-uYK-yKdNGzRAj_h_XnRilW9maYmoOx-D3KFQZQtRsuMJ7Crgf7ivGpAkEOk-4oo7z-CekWI8TiSUsWbQJ5bb2Td2OYPZ45b4S6s", "link": "https://www.google.com/maps/place//data=!4m2!3m1!1s0x865b2d2f76d2be53:0x9aee4d078a09a6cd?sa=X&hl=en&gl=USl" }, "description": "INCOMING: Hoobstank & Lit will bring their Tried & True Tour to Haute Spot in Cedar Park, TX on Tuesday, Nov. 8! Alien Ant Farm & Kris Roe (of The Ataris) will open the show! Full concert schedule...", "ticket_info": [ { "source": "Closeseats.com", "link": "https://www.closeseats.com/cedar-park/alternative/hoobastank-and-lit-tickets/5285105", "link_type": "TICKETS" }, { "source": "Feefreeticket.com", "link": "https://www.feefreeticket.com/hoobastank-and-lit-haute-spot/5285105", "link_type": "TICKETS" }, { "source": "Bigtowntickets.com", "link": "https://www.bigtowntickets.com/Events/Alternative-Tickets/Hoobastank-and-Lit-2022-11-08-18-00-00", "link_type": "TICKETS" }, { "source": "Ticketsource.com", "link": "https://www.ticketsource.com/2135448/Hoobastank", "link_type": "TICKETS" }, { "source": "Facebook", "link": "https://m.facebook.com/events/haute-spot/hoobastank-lit-tried-true-tour-w-alien-ant-farm-kris-roe-of-the-ataris-at-haute-/3231075087211137/", "link_type": "MORE INFO" } ], "venue": { "name": "Haute Spot Event Venue", "rating": 4.4, "reviews": 349, "link": "https://www.google.com/search?hl=en&gl=USl&q=Haute+Spot+Event+Venue&ludocid=11163945221074036429&ibp=gwp%3B0,7" } }, ... other results ]
Использование API Google Events от SerpApi
В этом разделе показано сравнение решения «сделай сам» и нашего решения.
Основное отличие в том, что это более быстрый подход. Google Events API будет обходить блокировки со стороны поисковых систем и вам не придется создавать парсер с нуля и поддерживать его.
Во-первых, нам нужно установить google-search-results
:
pip install google-search-results
Импортируем необходимые библиотеки для работы:
from serpapi import GoogleSearch import os, json
Далее пишем поисковый запрос и необходимые параметры для создания запроса:
params = { # https://docs.python.org/3/library/os.html#os.getenv 'api_key': os.getenv('API_KEY'), # your serpapi api 'q': 'Events in Austin', # search query 'engine': 'google_events', # SerpApi search engine 'hl': 'en', # language 'gl': 'us', # country of the search 'start': 0 # pagination }
Объявление списка google_events_results
, куда будут добавлены извлеченные данные:
google_events_results = []
Так как мы хотим извлечь все данные, нам нужно использовать параметр 'start'
, отвечающий за пагинацию.
Давайте реализуем бесконечный цикл, который будет увеличивать значение параметра 'start'
на 10 на каждой итерации. Это будет продолжаться до тех пор, пока есть что извлекать:
while True: search = GoogleSearch(params) # where data extraction happens on the SerpApi backend result_dict = search.get_dict() # JSON -> Python dict if 'error' in result_dict: break # data extraction will be here params['start'] += 10
Данные извлекаются довольно просто, нам достаточно обратиться к ключу 'events_results'
.
for result in result_dict['events_results']: google_events_results.append(result)
Пример кода для интеграции:
from serpapi import GoogleSearch import os, json params = { # https://docs.python.org/3/library/os.html#os.getenv 'api_key': os.getenv('API_KEY'), # your serpapi api 'q': 'Events in Austin', # search query 'engine': 'google_events', # SerpApi search engine 'hl': 'en', # language 'gl': 'us', # country of the search 'start': 0 # pagination } google_events_results = [] while True: search = GoogleSearch(params) # where data extraction happens on the SerpApi backend result_dict = search.get_dict() # JSON -> Python dict if 'error' in result_dict: break for result in result_dict['events_results']: google_events_results.append(result) params['start'] += 10 print(json.dumps(google_events_results, indent=2, ensure_ascii=False))
Выход:
[ { "title": "Lit @ Haute Spot", "date": { "start_date": "Nov 8", "when": "Tue, Nov 8, 5 – 10 PM CST" }, "address": [ "Haute Spot Event Venue, 1501 E New Hope Dr", "Cedar Park, TX" ], "link": "https://m.facebook.com/events/haute-spot/hoobastank-lit-tried-true-tour-w-alien-ant-farm-kris-roe-of-the-ataris-at-haute-/3231075087211137/", "event_location_map": { "image": "https://www.google.com/maps/vt/data=Mi6idsSxpVUEprl-uYK-yKdNGzRAj_h_XnRilW9maYmoOx-D3KFQZQtRsuMJ7Crgf7ivGpAkEOk-4oo7z-CekWI8TiSUsWbQJ5bb2Td2OYPZ45b4S6s", "link": "https://www.google.com/maps/place//data=!4m2!3m1!1s0x865b2d2f76d2be53:0x9aee4d078a09a6cd?sa=X", "serpapi_link": "https://serpapi.com/search.json?data=%214m2%213m1%211s0x865b2d2f76d2be53%3A0x9aee4d078a09a6cd&engine=google_maps&google_domain=google.com&hl=en&q=Events+in+Austin&start=0&type=place" }, "description": "INCOMING: Hoobstank & Lit will bring their Tried & True Tour to Haute Spot in Cedar Park, TX on Tuesday, Nov. 8! Alien Ant Farm & Kris Roe (of The Ataris) will open the show! Full concert schedule...", "ticket_info": [ { "source": "Closeseats.com", "link": "https://www.closeseats.com/cedar-park/alternative/hoobastank-and-lit-tickets/5285105", "link_type": "tickets" }, { "source": "Feefreeticket.com", "link": "https://www.feefreeticket.com/hoobastank-and-lit-haute-spot/5285105", "link_type": "tickets" }, { "source": "Bigtowntickets.com", "link": "https://www.bigtowntickets.com/Events/Alternative-Tickets/Hoobastank-and-Lit-2022-11-08-18-00-00", "link_type": "tickets" }, { "source": "Ticketsource.com", "link": "https://www.ticketsource.com/2135448/Hoobastank", "link_type": "tickets" }, { "source": "Facebook", "link": "https://m.facebook.com/events/haute-spot/hoobastank-lit-tried-true-tour-w-alien-ant-farm-kris-roe-of-the-ataris-at-haute-/3231075087211137/", "link_type": "more info" } ], "venue": { "name": "Haute Spot Event Venue", "rating": 4.4, "reviews": 349, "link": "https://www.google.com/search?q=Haute+Spot+Event+Venue&ludocid=11163945221074036429&ibp=gwp%3B0,7" }, "thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTYdxz7IF7Vmp9CMyaqElXJbl7oqMaSSTrtGaXFa4U&s", "image": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRTjb5umquoSVIOAaLDN06mjBdTz-6OzVCACDu43lSXmA&s=10" }, ... other results ]
Ссылки
Первоначально опубликовано на SerpApi: https://serpapi.com/blog/scrape-google-events-results-with-python/
Присоединяйтесь к нам в Твиттере | "YouTube"
Добавьте Запрос функции💫 или Ошибку🐞