Веб-соскабливание страницы JavaScript с Python
Я пытаюсь разработать простой скребок. Я хочу извлечь текст без кода HTML. Фактически, я достигаю этой цели, но я видел, что на некоторых страницах, где загружен JavaScript, я не получил хороших результатов.
Например, если какой-то код JavaScript добавляет некоторый текст, я не вижу его, потому что, когда я вызываю
response = urllib2.urlopen(request)
Я получаю исходный текст без добавленного (потому что JavaScript выполняется на клиенте).
Итак, я ищу некоторые идеи для решения этой проблемы.
Ответы
Ответ 1
EDIT 30/Dec/2017: Этот ответ появляется в результатах поиска Google, поэтому я решил его обновить. Старый ответ все еще в конце.
dryscape больше не поддерживается, и разработчики библиотеки dryscape рекомендуют использовать только Python 2. Я нашел использование Selenium python library с Phantom JS в качестве веб-драйвера достаточно быстро и легко, чтобы выполнить эту работу.
После установки Phantom JS убедитесь, что phantomjs
файл 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
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 для очистки веб-сайтов, управляемых 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>
Ответ 2
Мы не получаем правильных результатов, потому что любой контент, сгенерированный javascript, должен отображаться в DOM. Когда мы выбираем HTML-страницу, мы получаем исходную, неизмененную с помощью JavaScript, DOM.
Поэтому нам нужно визуализировать содержимое javascript перед сканированием страницы.
Поскольку selenium уже упоминался в этой теме много раз (и иногда упоминалось также о его медленной скорости), я перечислю два других возможных решения.
Решение 1. Это очень хорошее руководство по использованию Scrapy для сканирования содержимого, созданного с помощью javascript, и мы собираемся следовать этому.
Что нам понадобится:
Докер установлен на нашей машине. До этого момента это преимущество перед другими решениями, поскольку оно использует платформу, независимую от ОС.
Установите Splash, следуя инструкциям, приведенным для нашей соответствующей ОС.
Цитирование из заставки:
Splash - это сервис рендеринга JavaScript. Это легкий веб-браузер с HTTP API, реализованный в Python 3 с использованием Twisted и QT5.
По сути, мы собираемся использовать Splash для визуализации сгенерированного Javascript контента.
Запустите сервер заставки: sudo docker run -p 8050:8050 scrapinghub/splash
.
Установите плагин scrapy-splash: pip install scrapy-splash
Предполагая, что у нас уже есть проект Scrapy (если нет, , давайте создадим один), мы будем следовать руководству и обновим settings.py
:
Затем перейдите к своим проектам Scrapy settings.py
и установите эти промежуточные программы:
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'
Наконец, мы можем использовать 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 (например, просмотр веб-страниц) максимально простым и интуитивно понятным.
Установить запросы-HTML: pipenv install requests-html
Сделайте запрос на страницу URL:
from requests_html import HTMLSession
session = HTMLSession()
r = session.get(a_page_url)
Отобразите ответ, чтобы получить сгенерированные Javascript биты:
r.html.render()
Наконец, модуль, кажется, предлагает возможности очистки.
Кроме того, мы можем попробовать хорошо документированный способ использования BeautifulSoup с объектом r.html
, который мы только что визуализировали.
Ответ 3
Возможно, selenium может это сделать.
from selenium import webdriver
import time
driver = webdriver.Firefox()
driver.get(url)
time.sleep(5)
htmlSource = driver.page_source
Ответ 4
Если вы когда-либо использовали модуль 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, демонстрирующий работу модуля.
Ответ 5
Это, похоже, тоже хорошее решение, взятое из замечательного сообщения в блоге
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
Ответ 6
Похоже, что данные, которые вы действительно ищете, можно получить через вторичный URL-адрес, называемый некоторым javascript на главной странице.
Хотя вы можете попробовать запустить javascript на сервере, чтобы справиться с этим, более простым подходом может быть загрузка страницы с помощью Firefox и использование такого инструмента, как Charles или Firebug, чтобы точно определить, что это за вторичный URL. Затем вы можете просто запросить этот URL непосредственно для интересующих вас данных.
Ответ 7
Селен лучше всего подходит для очистки содержимого 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)
Легко, правда?
Ответ 8
Вы также можете выполнить javascript, используя webdriver.
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')
Ответ 9
Лично я предпочитаю использовать скрап и selenium и докеризацию как в отдельных контейнерах. Таким образом, вы можете установить как с минимальными хлопотами, так и сканировать современные веб-сайты, которые почти все содержат 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)
Настоящее волшебство происходит в middlewares.py. Перезапишите два метода в промежуточном программном обеспечении загрузчика, __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 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 совпадает с именем переменной среды, которую мы передали нашему контейнеру скребка (здесь это было 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, и вы можете получить его здесь here
Ответ 10
Вы хотите использовать urllib, запросы, красивыйSoup и selenium веб-драйвер в вашем script для разных частей страницы (чтобы назвать несколько).
Иногда вы получаете то, что вам нужно, только с одним из этих модулей.
Иногда вам понадобятся два, три или все эти модули.
Иногда вам нужно отключить js в вашем браузере.
Иногда вам понадобится информация заголовка в script.
Никакие веб-сайты не могут быть очищены одинаково, и ни один веб-сайт не может быть очищен таким же образом навсегда, без необходимости изменять ваш искатель, как правило, через несколько месяцев. Но их всех можно очистить! Там, где есть, есть способ наверняка.
Если вам нужны очищенные данные в будущем, просто очистите все, что вам нужно, и сохраните их в файлах .dat с рассолом.
Просто продолжайте поиск, как попробовать, что с этими модулями, и копировать и вставлять свои ошибки в Google.
Ответ 11
Сочетание BeautifulSoup и Selenium работает очень хорошо для меня.
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 bs4 import BeautifulSoup as bs
driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
try:
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "myDynamicElement"))) #waits 10 seconds until element is located. Can have other wait conditions such as visibility_of_element_located or text_to_be_present_in_element
html = driver.page_source
soup = bs(html, "lxml")
dynamic_text = soup.find_all("p", {"class":"class_name"}) #or other attributes, optional
else:
print("Couldnt locate element")
P.S. Вы можете найти больше условий ожидания здесь
Ответ 12
Использование PyQt5
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEnginePage
import sys
import bs4 as bs
import urllib.request
class Client(QWebEnginePage):
def __init__(self,url):
global app
self.app = QApplication(sys.argv)
QWebEnginePage.__init__(self)
self.html = ""
self.loadFinished.connect(self.on_load_finished)
self.load(QUrl(url))
self.app.exec_()
def on_load_finished(self):
self.html = self.toHtml(self.Callable)
print("Load Finished")
def Callable(self,data):
self.html = data
self.app.quit()
#url = ""
#client_response = Client(url)
#print(client_response.html)