Соскребая веб-страницу JavaScript с Python - PullRequest
145 голосов
/ 08 ноября 2011

Я пытаюсь разработать простой веб-скребок.Я хочу извлечь текст без кода HTML.Фактически, я достигаю этой цели, но я видел, что на некоторых страницах, где загружен JavaScript, я не получил хороших результатов.

Например, если какой-то код JavaScript добавляет какой-то текст, я не вижупотому что, когда я звоню

response = urllib2.urlopen(request)

, я получаю исходный текст без добавленного (потому что JavaScript выполняется в клиенте).

Итак, я ищу некоторые идеи длярешить эту проблему.

Ответы [ 12 ]

188 голосов
/ 18 октября 2014

РЕДАКТИРОВАТЬ 30 / Dec / 2017: этот ответ появляется в лучших результатах поиска Google, поэтому я решил обновить его.Старый ответ все еще в конце.

Dryscape больше не поддерживается, и разработчики библиотеки Dryscape рекомендуют использовать только Python 2.Я обнаружил, что использование библиотеки Python Selenium с Phantom JS в качестве веб-драйвера достаточно быстрое и простое для выполнения работы.

После установки Phantom JS убедитесь, что бинарный файл phantomjsдоступен по текущему пути:

phantomjs --version
# result:
2.1.1

Пример

Чтобы привести пример, я создал образец страницы со следующим HTML-кодом.( ссылка ):

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Javascript scraping test</title>
</head>
<body>
  <p id='intro-text'>No javascript support</p>
  <script>
     document.getElementById('intro-text').innerHTML = 'Yay! Supports javascript';
  </script> 
</body>
</html>

без JavaScript, он говорит: No javascript support и с JavaScript: Yay! Supports javascript

Соскоб без поддержки JS:

import requests
from bs4 import BeautifulSoup
response = requests.get(my_url)
soup = BeautifulSoup(response.text)
soup.find(id="intro-text")
# Result:
<p id="intro-text">No javascript support</p>

Очистка с поддержкой JS:

from selenium import webdriver
driver = webdriver.PhantomJS()
driver.get(my_url)
p_element = driver.find_element_by_id(id_='intro-text')
print(p_element.text)
# result:
'Yay! Supports javascript'

Вы также можете использовать библиотеку Python dryscrape для очистки веб-сайтов на основе JavaScript.

Соскоб с поддержкой JS:

import dryscrape
from bs4 import BeautifulSoup
session = dryscrape.Session()
session.visit(my_url)
response = session.body()
soup = BeautifulSoup(response)
soup.find(id="intro-text")
# Result:
<p id="intro-text">Yay! Supports javascript</p>
47 голосов
/ 30 мая 2018

Мы не получаем правильных результатов, потому что любой контент, сгенерированный javascript, должен отображаться в DOM.Когда мы выбираем HTML-страницу, мы выбираем исходную, неизмененную с помощью javascript, DOM.

Поэтому нам нужно визуализировать содержимое javascript перед сканированием страницы.

Поскольку селен уже упоминался, многиераз в этой теме (и о том, как медленно это происходит, иногда упоминалось также) я перечислю два других возможных решения.


Решение 1: Это очень хороший учебник по как использовать Scrapy для сканирования контента, сгенерированного javascript , и мы собираемся следовать этому.

Что нам понадобится:

  1. Докер установлен на нашей машине.До этого момента это преимущество перед другими решениями, поскольку оно использует платформу, независимую от ОС.

  2. Установите Splash , следуя инструкциям, приведенным для нашей соответствующей ОС.1028 * Цитирование из документации по заставке:

    Всплеск - это сервис рендеринга JavaScript.Это легкий веб-браузер с HTTP API, реализованный в Python 3 с использованием Twisted и QT5.

    По сути, мы будем использовать Splash для рендеринга сгенерированного Javascript контента.

  3. Запустить сервер заставок: sudo docker run -p 8050:8050 scrapinghub/splash.

  4. Установите плагин scrapy-splash : pip install scrapy-splash

  5. Предполагая, что у нас уже создан проект Scrapy (если нет, давайтесделайте один ), мы последуем руководству и обновим settings.py:

    Затем перейдите к settings.py вашего проекта Scrapy и установите эти промежуточные программы:

    DOWNLOADER_MIDDLEWARES = {
          'scrapy_splash.SplashCookiesMiddleware': 723,
          'scrapy_splash.SplashMiddleware': 725,
          'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
    }
    

    URL-адрес сервера Splash (если вы используете Win или OSX, это должен быть URL-адрес докера: Как получить IP-адрес контейнера Docker с хоста? ):

    SPLASH_URL = 'http://localhost:8050'
    

    И, наконец, вам также необходимо установить следующие значения:

    DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
    HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
    
  6. Наконец, мы можем использовать SplashRequest:

    У обычного паука у вас есть объекты Request, которые вы можете использовать для открытия URL-адресов.Если страница, которую вы хотите открыть, содержит данные, сгенерированные JS, вы должны использовать SplashRequest (или SplashFormRequest) для отображения страницы.Вот простой пример:

    class MySpider(scrapy.Spider):
        name = "jsscraper"
        start_urls = ["http://quotes.toscrape.com/js/"]
    
        def start_requests(self):
            for url in self.start_urls:
            yield SplashRequest(
                url=url, callback=self.parse, endpoint='render.html'
            )
    
        def parse(self, response):
            for q in response.css("div.quote"):
            quote = QuoteItem()
            quote["author"] = q.css(".author::text").extract_first()
            quote["quote"] = q.css(".text::text").extract_first()
            yield quote
    

    SplashRequest отображает URL-адрес в виде html и возвращает ответ, который можно использовать в методе обратного вызова (синтаксический анализ).


Решение 2: Давайте назовем этот эксперимент на данный момент (май 2018 г.) ...
Это решение предназначено только для версии Python 3.6 (на данный момент).

Знаете ли вы модуль запросы (ну, а кто нет)?
Теперь у него есть маленький брат, просматривающий веб: запросы-HTML :

Эта библиотека предназначена для того, чтобы сделать синтаксический анализ HTML (например, просмотр веб-страниц) максимально простым и интуитивно понятным.

  1. Установить запросы-html: pipenv install requests-html

  2. Сделайте запрос к URL страницы:

    from requests_html import HTMLSession
    
    session = HTMLSession()
    r = session.get(a_page_url)
    
  3. Отобразите ответ, чтобы получить сгенерированные Javascript биты:

    r.html.render()
    

Наконец, модуль, кажется, предлагает возможности очистки .
В качестве альтернативы, мы можем попробовать хорошо документированный способ использования BeautifulSoup с объектом r.html, который мы только что визуализировали.

40 голосов
/ 14 апреля 2016

Может быть селен может это сделать.

from selenium import webdriver
import time

driver = webdriver.Firefox()
driver.get(url)
time.sleep(5)
htmlSource = driver.page_source
16 голосов
/ 16 апреля 2018

Если вы когда-либо использовали модуль Requests для python, я недавно обнаружил, что разработчик создал новый модуль под названием Requests-HTML, который теперь также имеет возможность рендеринга JavaScript.

Вы также можете посетить https://html.python -requests.org / , чтобы узнать больше об этом модуле, или, если вас интересует только рендеринг JavaScript, вы можете посетить https://html.python -requests.org /? # javascript-support для непосредственного изучения того, как использовать модуль для визуализации JavaScript с использованием Python.

По существу, после правильной установки модуля Requests-HTML в следующем примере, который показан по приведенной выше ссылке , показано, как можно использовать этот модуль для очистки веб-сайта и рендеринга JavaScript, содержащегося в сайт:

from requests_html import HTMLSession
session = HTMLSession()

r = session.get('http://python-requests.org/')

r.html.render()

r.html.search('Python 2 will retire in only {months} months!')['months']

'<time>25</time>' #This is the result.

Я недавно узнал об этом из видео на YouTube. Нажмите здесь! , чтобы посмотреть видео на YouTube, демонстрирующее работу модуля.

14 голосов
/ 10 апреля 2016

Это также хорошее решение, взятое из отличного сообщения в блоге

import sys  
from PyQt4.QtGui import *  
from PyQt4.QtCore import *  
from PyQt4.QtWebKit import *  
from lxml import html 

#Take this class for granted.Just use result of rendering.
class Render(QWebPage):  
  def __init__(self, url):  
    self.app = QApplication(sys.argv)  
    QWebPage.__init__(self)  
    self.loadFinished.connect(self._loadFinished)  
    self.mainFrame().load(QUrl(url))  
    self.app.exec_()  

  def _loadFinished(self, result):  
    self.frame = self.mainFrame()  
    self.app.quit()  

url = 'http://pycoders.com/archive/'  
r = Render(url)  
result = r.frame.toHtml()
# This step is important.Converting QString to Ascii for lxml to process

# The following returns an lxml element tree
archive_links = html.fromstring(str(result.toAscii()))
print archive_links

# The following returns an array containing the URLs
raw_links = archive_links.xpath('//div[@class="campaign"]/a/@href')
print raw_links
12 голосов
/ 08 ноября 2011

Похоже, к данным, которые вы действительно ищете, можно получить доступ через вторичный URL-адрес, вызываемый каким-то javascript на основной странице.

Хотя вы можете попробовать запустить javascript на сервере, чтобы справиться с этим, более простым подходом может быть загрузка страницы с помощью Firefox и использование таких инструментов, как Charles или Firebug для точно определить, что это за вторичный URL. Затем вы можете просто запросить этот URL непосредственно для данных, которые вас интересуют.

9 голосов
/ 18 января 2018

Селен лучше всего подходит для очистки содержимого JS и Ajax.

Проверьте эту статью для извлечения данных из Интернета с использованием Python

$ pip install selenium

Затем загрузите веб-драйвер Chrome.

from selenium import webdriver

browser = webdriver.Chrome()

browser.get("https://www.python.org/")

nav = browser.find_element_by_id("mainnav")

print(nav.text)

Полегче, верно?

6 голосов
/ 28 марта 2017

Вы также можете выполнить JavaScript с помощью веб-драйвера.

from selenium import webdriver

driver = webdriver.Firefox()
driver.get(url)
driver.execute_script('document.title')

или сохранить значение в переменной

result = driver.execute_script('var text = document.title ; return var')
5 голосов
/ 28 марта 2017

Вы захотите использовать в своем скрипте веб-драйвер urllib, запросы, beautifulSoup и selenium для разных частей страницы (если назвать несколько).
Иногда вы получаете то, что вам нужно, только с одним изэти модули.
Иногда вам потребуется два, три или все эти модули.
Иногда вам нужно отключить js в вашем браузере.
Иногда вам понадобится информация заголовка в вашем браузере.script.
Ни один веб-сайт не может быть удален одним и тем же способом, и ни один веб-сайт не может быть удален одним и тем же способом навсегда без необходимости изменять ваш сканер, обычно через несколько месяцев.Но все они могут быть очищены!Там, где есть желание, есть способ наверняка.
Если вам нужно постоянно собирать данные в будущем, просто соберите все, что вам нужно, и сохраните их в файлах .dat с маринадом.
Просто продолжайте искать, как это сделать с этими модулями.и копирование и вставка ваших ошибок в Google.

4 голосов
/ 30 мая 2018

Лично я предпочитаю использовать скрап и селен и докеризацию как в отдельных контейнерах.Таким образом, вы можете установить как с минимальными хлопотами, так и сканировать современные веб-сайты, которые почти все содержат javascript в той или иной форме.Вот пример:

Используйте scrapy startproject, чтобы создать свой скребок и написать свой паук, скелет может быть таким простым:

import scrapy


class MySpider(scrapy.Spider):
    name = 'my_spider'
    start_urls = ['https://somewhere.com']

    def start_requests(self):
        yield scrapy.Request(url=self.start_urls[0])


    def parse(self, response):

        # do stuff with results, scrape items etc.
        # now were just checking everything worked

        print(response.body)

Настоящая магия происходит в промежуточном программном обеспечении.ру.Перезапишите два метода в промежуточном программном обеспечении загрузчика, __init__ и process_request, следующим образом:

# import some additional modules that we need
import os
from copy import deepcopy
from time import sleep

from scrapy import signals
from scrapy.http import HtmlResponse
from selenium import webdriver

class SampleProjectDownloaderMiddleware(object):

def __init__(self):
    SELENIUM_LOCATION = os.environ.get('SELENIUM_LOCATION', 'NOT_HERE')
    SELENIUM_URL = f'http://{SELENIUM_LOCATION}:4444/wd/hub'
    chrome_options = webdriver.ChromeOptions()

    # chrome_options.add_experimental_option("mobileEmulation", mobile_emulation)
    self.driver = webdriver.Remote(command_executor=SELENIUM_URL,
                                   desired_capabilities=chrome_options.to_capabilities())


def process_request(self, request, spider):

    self.driver.get(request.url)

    # sleep a bit so the page has time to load
    # or monitor items on page to continue as soon as page ready
    sleep(4)

    # if you need to manipulate the page content like clicking and scrolling, you do it here
    # self.driver.find_element_by_css_selector('.my-class').click()

    # you only need the now properly and completely rendered html from your page to get results
    body = deepcopy(self.driver.page_source)

    # copy the current url in case of redirects
    url = deepcopy(self.driver.current_url)

    return HtmlResponse(url, body=body, encoding='utf-8', request=request)

Не забудьте включить это промежуточное ПО, раскомментировав следующие строки в файле settings.py:

DOWNLOADER_MIDDLEWARES = {
'sample_project.middlewares.SampleProjectDownloaderMiddleware': 543,}

Далее для докеризации.Создайте Dockerfile из облегченного образа (здесь я использую python Alpine), скопируйте в него каталог вашего проекта, установите требования:

# Use an official Python runtime as a parent image
FROM python:3.6-alpine

# install some packages necessary to scrapy and then curl because it's  handy for debugging
RUN apk --update add linux-headers libffi-dev openssl-dev build-base libxslt-dev libxml2-dev curl python-dev

WORKDIR /my_scraper

ADD requirements.txt /my_scraper/

RUN pip install -r requirements.txt

ADD . /scrapers

И, наконец, соберите все вместе в docker-compose.yaml:

version: '2'
services:
  selenium:
    image: selenium/standalone-chrome
    ports:
      - "4444:4444"
    shm_size: 1G

  my_scraper:
    build: .
    depends_on:
      - "selenium"
    environment:
      - SELENIUM_LOCATION=samplecrawler_selenium_1
    volumes:
      - .:/my_scraper
    # use this command to keep the container running
    command: tail -f /dev/null

Выполнить docker-compose up -d.Если вы делаете это в первый раз, потребуется некоторое время, чтобы он извлек последнюю версию селен / автономный хром, а также построил ваш скребковый образ.

Как только это будет сделано, вы можете проверить, что ваши контейнеры работают с docker ps, а также убедиться, что имя контейнера селена совпадает с именем переменной среды, которую мы передали нашему контейнеру скребка (здесь это былоSELENIUM_LOCATION=samplecrawler_selenium_1).

Введите свой контейнер для скребка с помощью docker exec -ti YOUR_CONTAINER_NAME sh, команда для меня была docker exec -ti samplecrawler_my_scraper_1 sh, перейдите в нужный каталог и перейдите к вашему скребку с помощью scrapy crawl my_spider.

Все это на моемGitHub страницу, и вы можете получить его от здесь

...