Важный момент для аналитиков данных — где взять данные и максимально дешево, поскольку для обучения нейронных сетей их нужно много. Те, кто эти данные имеет, копит и улучшает расположены к тому, чтобы продать их подороже. При анализе данных нет уверенности, что работа с данными окупится — это инвестиции. Вполне здоровое желательно вложится по-минимуму и получить побольше. 🙂
Новостные статьи с Bloomberg — значительный объем текстовой информации различных тематик, интересный для анализа. Как получить их, учитывая, что для новостного агенства доступ к этой информации — их заработок и они тщательно оберегают, чтобы нахаляву получить было бы нельзя.
Код в Colab доступен по ссылке. Сразу отмечу, это экспериментальный код, там может быть много лишнего и я с ним играюсь по мере необходимости, так что он меняется.
Парсинг Google ссылок
Первый момент — как получить ссылки на новостные статьи? На сайте Bloomberg нет какого-то общего списка новостных статей. В день их выходит тысячи. Изучение файла https://www.bloomberg.com/robots.txt, чтобы получить sitemap.xml результата не дал. Описаний новостной части сайта нет. Воспользуемся поисковиком Google для получения ссылок на новостные статьи.
В материале Current Google Search Packages using Python3.7: A Simple Tutorial дано подробное описание возможных вариантов, как получить ссылки. Я остановлюсь на двух бесплатных вариантах, хотя придерживаюсь мнения автора, что надежнее воспользоваться легальным подходом через Google API.
Импортируем необходимые для работы библиотеки:
#@title Код импорта библиотек
# to hide output of this cell
#%%capture
!pip install selenium
!apt-get update # to update ubuntu to correctly run apt install
!apt install chromium-chromedriver
!cp /usr/lib/chromium-browser/chromedriver /usr/bin
import sys
sys.path.insert(0,'/usr/lib/chromium-browser/chromedriver')
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
import pandas as pd
import numpy as np
from bs4 import BeautifulSoup
#import chromedriver_binary
import string
import time
from datetime import datetime
from google.colab import files
import re
from json import loads
!pip install fake-useragent
from fake_useragent import UserAgent
pd.options.display.float_format = '{:.0f}'.formatПодгружаем драйвер браузера Chrome:
#@title Код загрузки драйвера Chrome
def loadChrome():
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
ua = UserAgent()
user_agent = "user-agent=" + ua.random
print("User-agent:", user_agent)
chrome_options.add_argument(user_agent)
browser = webdriver.Chrome('chromedriver', chrome_options = chrome_options)
return browserЕсли в паре с Selenium симпатичнее использовать Firefox, то здесь пример работы.
Загружаем браузер: browser = loadChrome()
Библиотека Google
- Бесплатная.
- Развивается с 2016 года.
- Последний релиз был в декабре 2019 (сейчас июнь 2020).
- Нужно минимум 2 сек. задержка, чтобы Google не блочил запросы.
- В выдаче максимум 100 линков, поскольку при первой загрузке Google в pagination отдает только 10 страниц. Библиотека не может проходить по ссылкам, чтобы подгружать новые страницы после 10-й. Нужно вручную это делать.
!pip install google
try:
from googlesearch import search
except ImportError:
print("No module named 'google' found") Параметры вызова метода для получения ссылок:
search(query, tld=’com’, lang=’en’, num=10, start=0, stop=None, pause=2.0)
- query : query string that we want to search for.
- tld : tld stands for top level domain which means we want to search our result on google.com or google.in or some other domain.
- lang : lang stands for language.
- num : Number of results we want.
- start : First result to retrieve.
- stop : Last result to retrieve. Use None to keep searching forever.
- pause : Lapse to wait between HTTP requests. Lapse too short may cause Google to block your IP. Keeping significant lapse will make your program slow but its safe and better option.
- Return : Generator (iterator) that yields found URLs. If the stop parameter is None the iterator will loop forever.
Задаем исходные параметры для
#@markdown ### Дата статей
articles_date = '2020-06-03' #@param {type:"date"}
#@markdown ---
#@markdown ### Количество статей:
num_of_articles = 5000 #@param {type:"integer"}
#@markdown ### Максимальное количество (0 - бесконечно):
stop = 0 #@param {type:"integer"}
#@markdown ### Задержка:
delay = 5 #@param {type:"slider", min:2, max:10, step:1}Google выдает результаты максимум на 10 страницах. Эта библиотека парсит только эти 10 страниц, не пытаясь переходить дальше. Хотя количество найденных линков отображается на первой странице Google И эту информацию можно легко использовать, чтобы собрать все ссылки.
Получаем ссылки:
query = "site:bloomberg.com/news/articles/" + articles_date + "/*"
if stop == 0:
stop = None
print("Запросил:", num_of_articles, "ссылок.")
print("На дату:", articles_date)
links = []
for j in search(query, tld="com", num = num_of_articles, stop = stop, pause = delay):
#print(j)
links.append(j)
print("Получено ссылок:", len(links))
a = [print(str(index + 1) + ":", link) for index, link in enumerate(links) if (link != None) and (link.strip() != '')] На выходе ровно 10 ссылок на страницу * 10 страниц = 100 ссылок.
Запросил: 5000 ссылок. На дату: 2020-06-03 Получено ссылок: 100 1: https://www.bloomberg.com/news/articles/2020-06-03/campaign-update 2: https://www.bloomberg.com/news/articles/2020-06-03/-opec 3: https://www.bloomberg.com/news/articles/2020-06-03/coronavirus-is-disproportionately-impacting-women ....
Отправка Google запроса через Selenium (Chrome)
Для получения страницы Google с результатами поискового запроса используется вот такой вариант, максимально имитирующий работу живого человека.
#@title Получаем web страницу
def getGoogleResultPage(browser, query):
print(query)
browser.get('http://www.google.com/') #?q=' + request)
search = browser.find_element_by_name('q')
search.send_keys(query)
search.send_keys(Keys.RETURN) # hit return after you enter search text
#time.sleep(5) # sleep for 5 seconds so you can see the results
#browser.quit()
return browser.page_sourceВ приницпе, это избыточно, поскольку Google нормально воспринимает POST запрос в строке вида: https://www.google.com/search?q=site%3Abloomberg.com%2Fnews%2Farticles%2F2020-06-03%2F*
browser = loadChrome() query = "site:bloomberg.com/news/articles/" + articles_date + "/*" html_source = getGoogleResultPage(browser, query)
Теперь распарсим полученный html c помощью BeautifulSoup.
def getGoogleSearch(soap):
google_links = [entry for entry in soup.find_all(class_='g')]
data = []
for links in google_links:
header = links.find_all('h3')
link = links.find_all('a')
data.append([header[0].text, link[0]['href']])
return datasoup = BeautifulSoup(html_source, 'lxml') data = getGoogleSearch(html_source) b = [print(d[0], "\t", d[1]) for d in data]
Получаем 10 результатов выдачи на странице:
2020 Presidential Election: Live Updates, News ... - Bloomberg https://www.bloomberg.com/news/articles/2020-06-03/campaign-update OPEC+ Unity Shaken as Iraq Pushed to Atone for Oil Cheating ... https://www.bloomberg.com/news/articles/2020-06-03/-opec Coronavirus Is Disproportionately Impacting Women ... https://www.bloomberg.com/news/articles/2020-06-03/coronavirus-is-disproportionately-impacting-women 2020 Presidential Election: Live Updates, News ... - Bloomberg https://www.bloomberg.com/news/articles/2020-06-03/campaign-update?srnd=markets-vp ...
Плюс внизу есть footer с pagination. Из него надо взять ссылки на остальные страницы:
#Функция для получения ссылок на страницы из footer-а
def getPaginationLinks(soup):
foot_links = [entry for entry in soup.find_all('div', {'id': 'foot', 'role' : "navigation"})]
page_links = []
if (foot_links != None):
f1_links = foot_links[0].find_all('a', {'class' : 'fl'})
for f1 in f1_links:
page_links.append([f1['aria-label'].replace("Page ", ""), f1['href']])
return page_linkspage_links = getPaginationLinks(soup) b = [print(d[0], "\t", d[1]) for d in page_links]
9 страниц результатов:
2 /search?q=site:bloomberg.com/news/articles/2020-06-03/*&ei=DRfZXrXsKrLWmAWTibH4AQ&start=10&sa=N&ved=2ahUKEwj1ppPAwOjpAhUyK6YKHZNEDB8Q8tMDegQICxAs 3 /search?q=site:bloomberg.com/news/articles/2020-06-03/*&ei=DRfZXrXsKrLWmAWTibH4AQ&start=20&sa=N&ved=2ahUKEwj1ppPAwOjpAhUyK6YKHZNEDB8Q8tMDegQICxAu 4 /search?q=site:bloomberg.com/news/articles/2020-06-03/*&ei=DRfZXrXsKrLWmAWTibH4AQ&start=30&sa=N&ved=2ahUKEwj1ppPAwOjpAhUyK6YKHZNEDB8Q8tMDegQICxAw ....
Теперь вопрос, Google сообщил, что найдено порядка 2000 страниц, а отобразилось только 10. Где остальные? А они появляются только если кликнуть на последние страницы. Можно получить все ссылки последовательно распарсивая страниц поисковой выдачи и получая pagination. Ну или можно просто воспользоваться функцией:
#Получаем новую страницу с результатами поиска #last_link - массив из предыдущего запроса вида [номер страницы, ссылка] #page_num - номер страницы #Функция возвращает новую ссылку для получения списка линков def getNewSearchPage(last_link, page_num = 0): start = "start=" + str((int(last_link[0]) - 1) * 10) return [page_num, "http://www.google.com" + last_link[1].replace(start, "start=" + str((page_num - 1) * 10))]
В качестве аргумента передается ссылка полученная на предыдущем шаге и номер страницы с результатами поиска:
link = getNewSearchPage(page_links[0], 12) link
На выходе получаем новую страницу для парсинга ссылок с результатами поискового запроса:
[12, 'http://www.google.com/search?q=site:bloomberg.com/news/articles/2020-06-03/*&ei=DRfZXrXsKrLWmAWTibH4AQ&start=110&sa=N&ved=2ahUKEwj1ppPAwOjpAhUyK6YKHZNEDB8Q8tMDegQICxAs']
Парсинг новостей Bloomberg
У нас есть ссылки на страницы Bloomberg, попробуем забрать новости с сайта. Как уже говорил ранее, Bloomberg не склонен отдавать забесплатно тексты статей, однако, Selenium достаточно хорошо эмулирует работу реального пользователя в браузере, поэтому Bloomberg отдает страницу. Естетственно, для работы в продакшене нужно использовать прокси для подмены IP адресов, чтобы минимизировать риск попадания на страницу с Captcha.
#Download any web page def getWebPage(browser, url): print(url) browser.get(url) return browser.page_source
Получаем новостную страницу Bloomberg, используя Selenium драйвер браузера — browser и передаем линк на новости полученные после парсинга Google запросов:
news = getWebPage(browser, data[1][1])
print("Размер HTML страницы:", len(news))
https://www.bloomberg.com/news/articles/2020-06-03/-opec
Размер HTML страницы: 852278Полученную страницу с новостями парсим BeautifulSoap-ом и преобразуем в строку текст, выкинув всю HTML разметку:
#Parse a news page from Bloomberg
def parseBloombergNewsPage(soup):
bloomberg_news = soup.find_all('div', {'class' : 'body-columns'})
text = ""
if (bloomberg_news != None and len(bloomberg_news) > 0):
paragraphs = bloomberg_news[0].find_all('p')
for paragraph in paragraphs:
text += " " + paragraph.text
return text.strip() Запускаем парсинг выбранной скачанной страницы Bloomberg:
news_soup = BeautifulSoup(news, 'lxml') text = parseBloombergNewsPage(news_soup)
Чтобы скачать с Colab полученный текст на локальный ПК:
#Download file
def downloadText(text, filename = ""):
if filename == "":
filename = 'bloomberg.html'
with open(filename, 'w') as f:
f.write(text)
files.download(filename)Скачиваем текст, вызвав эту функцию:
downloadText(text)