Pythonを使用したWebスクレイピングJavaScriptページ


178

シンプルなWebスクレイパーを開発しようとしています。HTMLコードなしでテキストを抽出したい。実際、私はこの目標を達成しましたが、JavaScriptがロードされている一部のページでは、良い結果が得られなかったことがわかりました。

たとえば、JavaScriptコードがテキストを追加する場合、それを表示できません。

response = urllib2.urlopen(request)

追加されたテキストなしで元のテキストを取得します(JavaScriptがクライアントで実行されるため)。

だから、私はこの問題を解決するためのいくつかのアイデアを探しています。


2
もっと重いものが必要なようですが、SeleniumまたはWatirを試してください。
WIM

2
私はこれをJavaで正常に実行しました(Cobraツールキットlobobrowser.org/cobra.jspを使用しました)Pythonでハッキングしたいので(常に良い選択です)、次の2つのオプションをお勧めします:-packtpub.com/article/ウェブスクレイピング-で-のpython-パート2 - blog.databigbang.com/web-scraping-ajax-and-javascript-sites
bpgergo

回答:


203

EDIT 30 / Dec / 2017:この回答はGoogle検索の上位結果に表示されるため、更新することにしました。古い答えはまだ終わりです。

dryscapeはもう保守されておらず、dryscape開発者が推奨するライブラリはPython 2のみです。SeleniumのpythonライブラリをPhantom JSとともにWebドライバーとして使用すると、十分な速度で簡単に作業を完了できます。

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で駆動されるWebサイトをスクレイピングすることもできます。

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>

16
残念ながら、Windowsはサポートされていません。
Expenzor 2017

1
Windows内でプログラミングしている人のための代替手段はありますか?
Hoshiko86

2
@Expenzor私は窓に取り組んでいます。PhantomJSは正常に動作します。
Aakash Choubey

17
PhantomJSが廃止され、現在ヘッドレスをサポートしているChromeに照らして、開発が活発に行われていないことは注目に値します。ヘッドレスchrome / firefoxの使用をお勧めします。
sytech

3
これは、セレンのサポートとPhantomJS自体の両方です。github.com/ariya/phantomjs/issues/15344
sytech

73

JavaScriptで生成されたコンテンツはすべてDOMでレンダリングする必要があるため、正しい結果が得られません。HTMLページをフェッチするときは、JavaScriptによって変更されていないDOMをフェッチします。

したがって、ページをクロールする前に、JavaScriptコンテンツをレンダリングする必要があります。

このスレッドではセレンがすでに何度も言及されているため(また、セレンがどれほど遅くなるかについても言及されているため)、他に2つの解決策を挙げます。


解決策1:これは、Scrapyを使用してJavaScriptで生成されたコンテンツをクロールする方法に関する非常に優れたチュートリアルです。

必要なもの:

  1. 私たちのマシンにインストールされているDocker。これは、OSに依存しないプラットフォームを利用しているため、これまでのところ他のソリューションより優れています。

  2. 対応するOSにリストされている指示に従ってSplashをインストールします
    スプラッシュドキュメントからの引用:

    スプラッシュは、JavaScriptレンダリングサービスです。ツイストとQT5を使用してPython 3で実装された、HTTP APIを備えた軽量のWebブラウザーです。

    基本的に、JavaScriptで生成されたコンテンツをレンダリングするためにSplashを使用します。

  3. スプラッシュサーバーを実行しますsudo docker run -p 8050:8050 scrapinghub/splash

  4. scrapy-splashプラグインをインストールします。pip install scrapy-splash

  5. 我々はすでにScrapyプロジェクトが作成したと仮定すると(そうでない場合は、レッツ・メイク1を)、私たちは、次のガイドに従って更新しますsettings.py

    次に、スクレイピープロジェクトに移動し、settings.py次のミドルウェアを設定します。

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

    SplashサーバーのURL(WinまたはOSXを使用している場合、これはDockerマシンのURLである必要があります:ホストからDockerコンテナーのIPアドレスを取得する方法?):

    SPLASH_URL = 'http://localhost:8050'

    そして最後に、これらの値も設定する必要があります。

    DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
    HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
  6. 最後に、以下を使用できますSplashRequest

    通常のスパイダーには、URLを開くために使用できるRequestオブジェクトがあります。開くページに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としてレンダリングし、callback(parse)メソッドで使用できる応答を返します。


解決策2:現時点(2018年5月)でこれを実験的と呼びましょう...
この解決策は、Pythonのバージョン3.6のみ(現時点では)です。

リクエストモジュールを知っていますか(よく知らない人)。
今それは小さな兄弟を要求するウェブを持っています:requests-HTML

このライブラリは、HTMLの解析(Webのスクレイピングなど)を可能な限りシンプルかつ直感的にすることを目的としています。

  1. requests-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ます。


.render()を呼び出した後、JSビットがロードされた完全なHTMLコンテンツを取得する方法を拡張できますか?それ以降は行き詰まっています。r.html.htmlオブジェクトのJavaScriptから通常ページに挿入されるすべてのiframeが表示されません。
anon58192932

@ anon58192932現時点ではこれは実験的な解決策であり、結果として何を達成しようとしているのか正確にはわからないので、実際には何も提案できません...解決策はまだ
完成

2
このエラーが発生しました:RuntimeError:既存のイベントループ内でHTMLSessionを使用できません。代わりにAsyncHTMLSessionを使用してください。
HuckIt

1
@HuckItこれは既知の問題のようです:github.com/psf/requests-html/issues/140
John Moutafis

47

多分セレンはそれを行うことができます。

from selenium import webdriver
import time

driver = webdriver.Firefox()
driver.get(url)
time.sleep(5)
htmlSource = driver.page_source

3
Seleniumはこの種のことに対して本当に重いです。それは不必要に遅くなり、PhantomJSを使用しない場合はブラウザヘッドが必要ですが、これは機能します。
ジョシュアヘッジス

@JoshuaHedges他のより標準的なブラウザをヘッドレスモードで実行できます。
reynoldsnlp

22

これまでRequestsにPython用のモジュールを使用したことがある場合、私は最近、開発者がRequests-HTMLJavaScriptをレンダリングする機能も備えた新しいモジュールを作成したことを最近知りました。

このモジュールの詳細については、https://html.python-requests.org/にアクセスすることもできます。JavaScriptのレンダリングにのみ関心がある場合は、https://html.python-requests.org/?#javascriptにアクセスできます。 -Pythonを使用してJavaScriptをレンダリングするモジュールの使用方法を直接学ぶためのサポート

基本的に、Requests-HTMLモジュールを正しくインストールすると、上記のリンクに示されている次の例は、このモジュールを使用してWebサイトをスクレイピングし、Webサイトに含まれる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ビデオを見る。


3
このモジュールはPython 3.6のみをサポートしていることに注意してください。
nat5142 2018年

1
このエラーが発生しました:SSLError:HTTPSConnectionPool(host = 'docs.python-requests.org'、port = 443):最大再試行回数は次のURLで超過しました:/(SSLError(SSLError(1、 '[SSL:TLSV1_ALERT_INTERNAL_ERROR] tlsv1アラートが原因)内部エラー(_ssl.c:1045) ')))
HuckIt

@HuckIt謝罪私はそのエラーに精通していませんが、エラーのように見えますが、アクセスしようとしているWebサイトにはSSL証明書関連の問題があった可能性があります。申し訳ありませんが、これは解決策ではありませんが、スタックオーバーフローで新しい質問をすることをお勧めします(まだ確認されていない場合)。
SShah

フードの下でクロムを使用しているようです。私にとっても
シド

14

これは優れたブログ投稿から取られた良い解決策のようです

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

本当に探しているデータは、プライマリページのJavaScriptによって呼び出されるセカンダリURLを介してアクセスできるようです。

サーバーでjavascriptを実行してこれを処理することもできますが、Firefoxを使用してページをロードし、CharlesFirebugなどのツールを使用して、セカンダリURLを正確に特定するという簡単な方法があります。次に、関心のあるデータについてそのURLを直接クエリできます。


@クリス誰かがこれにつまずいて、セレンほど重いものの代わりに試してみたいと思った場合に備えて、ここに短い例を示します。これにより、McMaster-Carr Webサイトで六角ナットの部品詳細ページが開きます。彼らのウェブサイトのコンテンツはほとんどがJavaScriptを使用して取得され、ネイティブページの情報はほとんどありません。ブラウザー開発者ツールを開いて、[ネットワーク]タブに移動し、ページを更新すると、ページから行われたすべてのリクエストを確認し、関連データ(この場合はパーツ詳細html)を見つけることができます。
SweepingsDemon

これは、Firefoxのdevtoolの[ネットワーク]タブにある別のURLです。これには、ほとんどのパーツ情報のhtmlが含まれ、他のパーツ情報に簡単に移動して簡単にスクレイピングを行うために必要なパラメーターの一部が表示されます。この特定の例は、価格が別のJavaScript関数によって生成されるため、特に有用ではありませんが、Stephenのアドバイスに従いたい人への紹介として十分に役立つはずです。
SweepingsDemon

12

Seleniumは、JSおよびAjaxコンテンツのスクレイピングに最適です。

Pythonを使用してWebからデータ抽出するには、この記事を確認してください

$ pip install selenium

次にChrome Webdriverをダウンロードします。

from selenium import webdriver

browser = webdriver.Chrome()

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

nav = browser.find_element_by_id("mainnav")

print(nav.text)

簡単ですよね?


8

webdriverを使用して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')

または、driver.titleプロパティを使用することもできます
Corey Goldberg

7

私は個人的には、スクレイピーとセレンを使用し、両方を別々のコンテナーでドッキングすることを好みます。この方法で、最小限の手間で両方をインストールし、ほとんどすべてのJavascriptが何らかの形式で含まれている最新のWebサイトをクロールできます。次に例を示します。

を使用し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で発生します。ダウンローダミドルウェアにおける2つのメソッドを上書きし、 __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アルパインを使用しています)、プロジェクトディレクトリは、それをコピーし、要件をインストールします。

# 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。これを初めて行う場合、最新のselenium / standalone-chromeをフェッチしてスクレイパーイメージをビルドするのにも時間がかかります。

完了したら、コンテナーが実行されdocker psていることを確認し、セレンコンテナーの名前が、スクレイパーコンテナーに渡した環境変数の名前と一致していることを確認します(ここではSELENIUM_LOCATION=samplecrawler_selenium_1)。

でスクレーパーコンテナーを入力します。docker exec -ti YOUR_CONTAINER_NAME shコマンドはでしdocker exec -ti samplecrawler_my_scraper_1 shた。適切なディレクトリにcdして、でスクレーパーを実行しますscrapy crawl my_spider

すべては私のgithubページにあり、ここから入手できます


5

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")

PS ここでより多くの待機条件を見つけることができます


4

ページのさまざまな部分のスクリプトで、urllib、requests、beautifulSoup、selenium Webドライバーを使用する必要があります(いくつか例を挙げます)。
これらのモジュールの1つだけで必要なものが得られる場合があります。
場合によっては、これらのモジュールの2つ、3つ、またはすべてが必要になります。
ブラウザーでjsをオフにする必要がある場合があります。
スクリプトでヘッダー情報が必要になる場合があります。
通常は数か月後にクローラーを変更することなく、同じ方法でWebサイトをスクレイピングしたり、同じ方法でWebサイトを永遠にスクレイピングしたりすることはできません。しかし、それらはすべて削ることができます!意志があるところに確かな方法があります。
将来にわたって継続的にスクレイピングされたデータが必要な場合は、必要なものすべてをスクレイピングし、それを.datファイルにpickleで保存してください。
これらのモジュールで何を試すかを検索し続け、エラーをGoogleにコピーして貼り付けてください。


3

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)

1

私はこの質問に対する答えを2日間探し続けています。多くの答えはあなたを別の問題に導きます。しかし、上記の蛇の答えは本当に要点です。これは、最短で最も簡単なソリューションです。最後の単語"var"変数名を表すので、次のように使用する必要があります。

 result = driver.execute_script('var text = document.title ; return text')

これは別の答えではなく、蛇の答えに対するコメントであるべきです。
イゼルビウス

1
それは明らかです。しかし、私はまだ50人の担当者がいないため、他の誰かの答えについてコメントすることはできません。
Abd_bgc

0

私は自分のいくつかのWebスクレイピングプロジェクトで同じ問題に対処しなければなりませんでした。JSをロードする代わりに、Pythonリクエストライブラリを使用してAPIに直接HTTPリクエストを送信することで対処しました。

Pythonリクエストライブラリはこれに適しています。また、inspect要素を使用してネットワークタブに移動することにより、httpリクエストを確認できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.