Python Requests (Web Scraping) - создание cookie для просмотра данных на веб-сайте.

Я пытаюсь очистить финансовый сайт, чтобы создать приложение, которое сравнивает точность финансовых данных с других сайтов (финансы Google/ Yahoo). Это личный проект, который я начал на самом деле просто для изучения программирования на Python и написания скриптов.

URL, который я пытаюсь проанализировать (в частности, "Ключевые данные", такие как рыночная капитализация, объем и т. Д.), Находится здесь:

https://www.marketwatch.com/investing/stock/sbux

Я выяснил (с помощью других), что файл cookie должен создаваться и отправляться с каждым запросом, чтобы страница отображала данные (в противном случае ответ html-страницы в значительной степени возвращается пустым).

Я использовал браузеры Opera/Firefox/Chrome для просмотра заголовков HTTP и запросов, которые отправляются обратно из браузера. Я пришел к выводу, что необходимо выполнить 3 шага / запроса, чтобы получить все данные cookie и построить их по частям.

Шаг / Запрос 1

Просто зайдя по вышеуказанному URL.

GET /investing/stock/sbux HTTP/1.1
Host: www.marketwatch.com:443
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 OPR/55.0.2994.44

HTTP/1.1 200 OK
Cache-Control: max-age=0, no-cache, no-store
Connection: keep-alive
Content-Length: 579
Content-Type: text/html; charset=utf-8
Date: Sun, 26 Aug 2018 05:12:16 GMT
Expires: Sun, 26 Aug 2018 05:12:16 GMT
Pragma: no-cache

Шаг / Запрос 2

Я не уверен, откуда взялся этот "POST" URL. Однако при использовании Firefox и просмотре сетевых подключений этот URL-адрес появился во вкладке "Trace Stack". Опять же, я понятия не имею, где взять этот URL, если он одинаков для всех или создан случайно. Я также не знаю, какие данные POST отправляются или откуда взяты значения X-Hash-Result или X-Token-Value. Однако этот запрос возвращает очень важное значение в заголовке ответа со следующей строкой: "Set-Cookie: ncg_g_id_zeta=701c19ee3f45d07b56b40fb8e313214d", этот фрагмент файла cookie имеет решающее значение для следующего запроса, чтобы вернуть полный файл cookie и получить данные о нем. веб-страница.

POST /149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/fingerprint HTTP/1.1
Host: www.marketwatch.com:443
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Content-Type: application/json; charset=UTF-8
Origin: https://www.marketwatch.com
Referer: https://www.marketwatch.com/investing/stock/sbux
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 OPR/55.0.2994.44
X-Hash-Result: 701c19ee3f45d07b56b40fb8e313214d
X-Token-Value: 900c4055-ef7a-74a8-e9ec-f78f7edc363b

HTTP/1.1 200 OK
Cache-Control: max-age=0, no-cache, no-store
Connection: keep-alive
Content-Length: 17
Content-Type: application/json; charset=utf-8
Date: Sun, 26 Aug 2018 05:12:16 GMT
Expires: Sun, 26 Aug 2018 05:12:16 GMT
Pragma: no-cache
Set-Cookie: ncg_g_id_zeta=701c19ee3f45d07b56b40fb8e313214d; Path=/; HttpOnly

Шаг / Запрос 3

Этот запрос отправляется на исходный URL с файлом cookie, выбранным на шаге 2. Затем в ответе возвращается полный файл cookie, который можно использовать на шаге 1, чтобы избежать повторения шагов 2 и 3 снова. Также будет отображена полная страница данных.

GET /investing/stock/sbux HTTP/1.1
Host: www.marketwatch.com:443
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: ncg_g_id_zeta=701c19ee3f45d07b56b40fb8e313214d
Referer: https://www.marketwatch.com/investing/stock/sbux
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 OPR/55.0.2994.44

HTTP/1.1 200 OK
Cache-Control: max-age=0, no-cache, no-store
Connection: keep-alive
Content-Encoding: gzip
Content-Length: 62944
Content-Type: text/html; charset=utf-8
Date: Sun, 26 Aug 2018 05:12:17 GMT
Expires: Sun, 26 Aug 2018 05:12:17 GMT
Pragma: no-cache
Server: Kestrel
Set-Cookie: seenads=0; expires=Sun, 26 Aug 2018 23:59:59 GMT; domain=.marketwatch.com; path=/
Set-Cookie: mw_loc=%7B%22country%22%3A%22CA%22%2C%22region%22%3A%22ON%22%2C%22city%22%3A%22MARKHAM%22%2C%22county%22%3A%5B%22%22%5D%2C%22continent%22%3A%22NA%22%7D; expires=Sat, 01 Sep 2018 23:59:59 GMT; domain=.marketwatch.com; path=/
Vary: Accept-Encoding
x-frame-options: SAMEORIGIN
x-machine: 8cfa9f20bf3eb

Резюме

Таким образом, шаг 2 является наиболее важным, чтобы получить оставшуюся часть куки... Но я не могу понять 3 вещи:

1) Откуда берется URL-адрес POST (не встроен в исходную страницу, является ли URL-адрес одинаковым для всех или он генерируется сайтом случайным образом).

2) Какие данные отправляются в запросе POST?

3) Откуда происходят X-Hash-Result и X-Token-Value? Обязательно ли отправлять в шапку с запросом?

Это было для меня хорошим испытанием, на которое я потратил несколько часов (я также очень плохо знаком с Python и HTTP Web Requests), и поэтому я чувствую, что кто-то с большим опытом может решить эту проблему более своевременно,

Спасибо всем за всех, кто может помочь.

1 ответ

Привет еще раз!

Сегодня вечером я потратил некоторое время, пытаясь заставить строку cookie начать работать. MarketWatch проделал довольно приличную работу по защите своих данных. Чтобы создать весь файл cookie, вам понадобится ключ API wsj (я думаю, поставщик финансовых данных их сайта) и некоторые скрытые переменные, которые потенциально доступны только для сервера клиента и строго скрыты в зависимости от вашего веб-драйвера или его отсутствия.

Например, если вы пытаетесь выполнить запросы: POST https://browser.pipe.aria.microsoft.com/Collector/3.0/?qsp=true&content-type=application/bond-compact-binary&client-id=NO_AUTH&sdk-version=ACT-Web-JS-2.7.1&x-apikey=c34cce5c21da4a91907bc59bce4784fb-42e261e9-5073-49df-a2e1-42415e012bc6-6954

Вы получите 400 несанкционированных ошибок.

Помните, что существует также большая вероятность того, что мастер кластера сервера хоста клиента и различные API-интерфейсы, с которыми он взаимодействует, обмениваются данными, и наши браузеры не могут получать сетевой трафик. Это может быть сделано через какое-то промежуточное программное обеспечение, например. Я считаю, что это может объяснить отсутствие значений X-Hash-Result и X-Token-Value.

Я не говорю, что невозможно создать эту строку cookie, просто это неэффективный путь с точки зрения времени и усилий на разработку. Я также сейчас подвергаю сомнению легкость масштабируемости этого метода с точки зрения использования различных тикеров помимо AAPL. Если нет явного требования не использовать веб-драйвер и / или сценарий не должен быть легко переносимым без какой-либо конфигурации, разрешенной вне установки pip, я бы не выбрал этот метод.

По сути, это оставляет нас либо с Scrapy Spider, либо с Selenium Scraper (и, к сожалению, с небольшой дополнительной конфигурацией среды, но очень важными навыками, которые необходимо выучить, если вы хотите писать и развертывать веб-скребки. Вообще говоря, запросы + bs4 предназначены для идеальных простых операций очистки / необычные потребности в переносимости кода).

Я разработал Selenium Scraper ETL Class, используя веб-драйвер PhantomJS. Он принимает строку тикера в качестве параметра и работает с другими акциями, кроме AAPL. Это было непросто, так как marketwatch.com не будет перенаправлять трафик с веб-драйвера PhantomJS (я могу сказать, что они потратили много ресурсов, пытаясь отговорить веб-скребков. Кстати, гораздо больше, чем, скажем, yahoo.com).

В любом случае, вот последний Selenium Script, он работает на питоне 2 и 3:

# Market Watch Test Scraper ETL
# Tested on python 2.7 and 3.5
# IMPORTANT: Ensure PhantomJS Web Driver is configured and installed

import pip
import sys
import signal
import time


# Package installer function to handle missing packages
def install(package):
    print(package + ' package for Python not found, pip installing now....')
    pip.main(['install', package])
    print(package + ' package has been successfully installed for Python\n Continuing Process...')

# Ensure beautifulsoup4 is installed
try:
    from bs4 import BeautifulSoup
except:
    install('beautifulsoup4')
    from bs4 import BeautifulSoup

# Ensure selenium is installed
try:
    from selenium import webdriver
    from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
except:
    install('selenium')
    from selenium import webdriver
    from selenium.webdriver.common.desired_capabilities import DesiredCapabilities


# Class to extract and transform raw marketwatch.com financial data
class MarketWatchETL:

    def __init__(self, ticker):
        self.ticker = ticker.upper()
        # Set up desired capabilities to spoof Firefox since marketwatch.com rejects any PhantomJS Request
        self._dcap = dict(DesiredCapabilities.PHANTOMJS)
        self._dcap["phantomjs.page.settings.userAgent"] = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) "
                                                           "AppleWebKit/537.36 (KHTML, like Gecko) "
                                                           "Chrome/29.0.1547.57 Safari/537.36")
        self._base_url = 'https://www.marketwatch.com/investing/stock/'
        self._retries = 10

    # Private Static Method to clean and organize Key Data Extract
    @staticmethod
    def _cleaned_key_data_object(raw_data):
        cleaned_data = {}
        raw_labels = raw_data['labels']
        raw_values = raw_data['values']
        i = 0
        for raw_label in raw_labels:
            raw_value = raw_values[i]
            cleaned_data.update({str(raw_label.get_text()): raw_value.get_text()})
            i += 1
        return cleaned_data

    # Private Method to scrap data from MarketWatch's web page
    def _scrap_financial_key_data(self):
        raw_data_obj = {}
        try:
            driver = webdriver.PhantomJS(desired_capabilities=self._dcap)
        except:
            print('***SETUP ERROR: The PhantomJS Web Driver is either not configured or incorrectly configured!***')
            sys.exit(1)
        driver.get(self._base_url + self.ticker)
        i = 0
        while i < self._retries:
            try:
                time.sleep(3)
                html = driver.page_source
                soup = BeautifulSoup(html, "html.parser")
                labels = soup.find_all('small', class_="kv__label")
                values = soup.find_all('span', class_="kv__primary")
                if labels and values:
                    raw_data_obj.update({'labels': labels})
                    raw_data_obj.update({'values': values})
                    break
                else:
                    i += 1
            except:
                i += 1
                continue
        if i == self._retries:
            print('Please check your internet connection!\nUnable to connect...')
            sys.exit(1)
        driver.service.process.send_signal(signal.SIGTERM)
        driver.quit()
        return raw_data_obj

    # Public Method to return a Stock's Key Data Object
    def get_stock_key_data(self):
        raw_data = self._scrap_financial_key_data()
        return self._cleaned_key_data_object(raw_data)


# Script's Main Process to test MarketWatchETL('TICKER')
if __name__ == '__main__':

    # Run financial key data extracts for Microsoft, Apple, and Wells Fargo
    msft_key_data = MarketWatchETL('MSFT').get_stock_key_data()
    aapl_key_data = MarketWatchETL('AAPL').get_stock_key_data()
    wfc_key_data = MarketWatchETL('WFC').get_stock_key_data()

    # Print result dictionaries
    print(msft_key_data.items())
    print(aapl_key_data.items())
    print(wfc_key_data.items())

Какие выводы:

dict_items([('Rev. per Employee', '$841.03K'), ('Short Interest', '44.63M'), ('Yield', '1.53%'), ('Market Cap', '$831.23B'), ('Open', '$109.27'), ('EPS', '$2.11'), ('Shares Outstanding', '7.68B'), ('Ex-Dividend Date', 'Aug 15, 2018'), ('Day Range', '108.51 - 109.64'), ('Average Volume', '25.43M'), ('Dividend', '$0.42'), ('Public Float', '7.56B'), ('P/E Ratio', '51.94'), ('% of Float Shorted', '0.59%'), ('52 Week Range', '72.05 - 111.15'), ('Beta', '1.21')])
dict_items([('Rev. per Employee', '$2.08M'), ('Short Interest', '42.16M'), ('Yield', '1.34%'), ('Market Cap', '$1.04T'), ('Open', '$217.15'), ('EPS', '$11.03'), ('Shares Outstanding', '4.83B'), ('Ex-Dividend Date', 'Aug 10, 2018'), ('Day Range', '216.33 - 218.74'), ('Average Volume', '24.13M'), ('Dividend', '$0.73'), ('Public Float', '4.82B'), ('P/E Ratio', '19.76'), ('% of Float Shorted', '0.87%'), ('52 Week Range', '149.16 - 219.18'), ('Beta', '1.02')])
dict_items([('Rev. per Employee', '$384.4K'), ('Short Interest', '27.44M'), ('Yield', '2.91%'), ('Market Cap', '$282.66B'), ('Open', '$58.87'), ('EPS', '$3.94'), ('Shares Outstanding', '4.82B'), ('Ex-Dividend Date', 'Aug 9, 2018'), ('Day Range', '58.76 - 59.48'), ('Average Volume', '18.45M'), ('Dividend', '$0.43'), ('Public Float', '4.81B'), ('P/E Ratio', '15.00'), ('% of Float Shorted', '0.57%'), ('52 Week Range', '49.27 - 66.31'), ('Beta', '1.13')])

Единственный дополнительный шаг, который вам нужно сделать перед запуском, это установить и настроить веб-драйвер PhantomJS в ваших средах развертывания. Если вам нужно автоматизировать развертывание такого веб-скребка, как этот, вы можете написать скрипт установки оболочки bash/power для предварительной настройки PhantomJS вашей среды.

Некоторые ресурсы для установки и настройки PhantomJS:

Установочные исполняемые файлы Windows/Mac PhantomJS

Руководство по установке Debian Linux PhantomJS

RHEL PhantomJS Руководство по установке

Если вы считаете это неполным ответом, то заранее прошу прощения. Я просто сомневаюсь в практичности и даже возможности сборки Cookie, как я рекомендовал в предыдущем посте.

Я думаю, что другая практическая возможность здесь - написать Scrapy Crawler, который я могу попытаться сделать для вас завтра вечером, если хотите.

Надеюсь, что все это помогает!

Другие вопросы по тегам