使用Python网页抓取JavaScript页面


175

我正在尝试开发一个简单的网页抓取工具。我想提取没有HTML代码的文本。实际上,我实现了这个目标,但是我发现在某些加载了JavaScript的页面中,我没有获得良好的结果。

例如,如果一些JavaScript代码添加了一些文本,则看不到它,因为当我调用

response = urllib2.urlopen(request)

我得到的原始文本没有添加文本(因为在客户端执行了JavaScript)。

因此,我正在寻找一些解决此问题的想法。


2
听起来您可能需要更重的东西,请尝试使用Selenium或Watir。
2011年

2
我已经在Java中成功完成了此操作(我使用了Cobra工具包lobobrowser.org/cobra.jsp)。由于您想入侵python(总是一个不错的选择),因此我建议您使用以下两个选项:-packtpub.com/article/ web的刮与-蟒-部分- 2 - blog.databigbang.com/web-scraping-ajax-and-javascript-sites
bpgergo

Answers:


202

编辑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驱动的网站。

借助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

1
Windows中编程的人还有其他选择吗?
Hoshiko86

2
@Expenzor我正在窗户上工作。PhantomJS正常工作。
Aakash Choubey

17
值得注意的是,鉴于Chrome现在支持无头,PhantomJS已停产并且不再处于积极开发中。建议使用无头铬/ Firefox。
sytech

3
它既是硒的支持,又是PhantomJS本身。github.com/ariya/phantomjs/issues/15344
sytech '18

73

我们无法获得正确的结果,因为任何JavaScript生成的内容都需要在DOM上呈现。当我们获取HTML页面时,我们将获取未经javascript修改的初始DOM。

因此,我们需要在爬网页面之前呈现javascript内容。

由于在该线程中已经多次提到硒(有时还提到硒的缓慢程度),因此我将列出另外两种可能的解决方案。


解决方案1:这是一个非常不错的教程,说明如何使用Scrapy来爬网javascript生成的内容,我们将遵循这一点。

我们将需要:

  1. 在我们的机器上安装了Docker。到目前为止,这是对其他解决方案的加分,因为它利用了与操作系统无关的平台。

  2. 按照针对我们相应操作系统列出的说明安装Splash
    从初始文档中引用:

    Splash是一种javascript渲染服务。这是一个带有HTTP API的轻量级Web浏览器,使用Twisted和QT5在Python 3中实现。

    本质上,我们将使用Splash渲染Javascript生成的内容。

  3. 运行启动服务器:sudo docker run -p 8050:8050 scrapinghub/splash

  4. 安装scrapy-splash插件:pip install scrapy-splash

  5. 假设我们已经创建了一个Scrapy项目(如果没有创建,请创建一个),我们将按照指南进行操作并更新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

    在普通蜘蛛中,您可以使用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并返回您可以在callback(parse)方法中使用的响应。


解决方案2:目前(2018年5月),我们将此称为实验性的。。。
此解决方案仅适用于Python的3.6版(当前)。

您知道请求模块(谁不知道)吗?
现在,它有了一个在网上爬行的小兄弟姐妹:requests-HTML

该库旨在使解析HTML(例如,抓取Web)尽可能简单直观。

  1. 安装requests-html: pipenv install requests-html

  2. 向页面的网址发出请求:

    from requests_html import HTMLSession
    
    session = HTMLSession()
    r = session.get(a_page_url)
  3. 渲染响应以获取Javascript生成的位:

    r.html.render()

最后,该模块似乎提供了抓取功能
另外,我们可以尝试使用r.html我们已渲染的对象使用BeautifulSoup的有据可查的方法。


在调用.render()之后,您可以扩展如何获取完整的HTML内容并加载JS位吗?在那之后我被困住了。我没有看到通常从r.html.html对象中的JavaScript注入到页面中的所有iframe 。
anon58192932 '18

@ anon58192932由于目前这是一个实验性的解决方案,因此我不知道您到底想实现什么目标,因此我真的无法提出任何建议...如果您还没有这样做,可以在此处创建一个新问题制定了一个解决方案
John Moutafis

2
我收到此错误:RuntimeError:无法在现有事件循环中使用HTMLSession。请改用AsyncHTMLSession。
HuckIt

1
@HuckIt这似乎是一个已知问题:github.com/psf/requests-html/issues/140
John Moutafis

46

也许能做到这一点。

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-HTML现在还具有呈现JavaScript的功能。

您还可以访问https://html.python-requests.org/以了解有关此模块的更多信息,或者,如果您仅对呈现JavaScript感兴趣,则可以访问https://html.python-requests.org/?#javascript -support直接学习如何使用模块使用Python渲染JavaScript。

本质上,正确安装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视频,该视频演示了该模块的工作原理。


3
请注意,此模块仅支持Python 3.6。
nat5142 '18 -10-12

1
我收到此错误:SSLError:HTTPSConnectionPool(host ='docs.python-requests.org',port = 443):url超出了最大重试次数:/(由SSLError(SSLError(1,'[[SSL:TLSV1_ALERT_INTERNAL_ERROR]内部错误(_ssl.c:1045)')))
HuckIt's

@HuckIt应用程序我不熟悉该错误,但是该错误似乎是您尝试访问的网站可能存在与SSL证书相关的问题。抱歉,这不是解决方案,但我建议您在堆栈溢出中提出一个新问题(如果尚未提出问题),并可能提供更多详细信息,例如您正在使用的网站url和代码。
SShah

似乎在引擎盖下使用铬。不过,对我来说效果很好
Sid

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中查询您感兴趣的数据。


@Kris以防万一有人偶然发现它并想尝试它而不是像硒这样重的东西,这是一个简短的例子。将在McMaster-Carr网站上打开六角螺母的零件详细信息页面。他们的网站内容大部分是使用Javascript获取的,并且几乎没有本机页面信息。如果打开浏览器开发人员工具,导航到“网络”选项卡,然后刷新页面,则可以查看该页面发出的所有请求并找到相关数据(在本例中为部件详细信息html)。
SweepingsDemon

是在Firefox devtool的“网络”选项卡中找到的另一个URL,如果遵循该URL,则包含大多数零件信息的html,并公开了一些必需的参数,这些参数可轻松导航到其他零件信息,从而更易于抓取。这个价格示例不是特别有用,因为价格是由另一个Javascript函数生成的,但是对于想要遵循Stephen的建议的任何人来说,它应该可以很好地用作介绍。
SweepingsDemon

12

硒最适合抓取JS和Ajax内容。

查看本文以使用Python从网络中提取数据

$ 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物业
科里·戈德堡

7

我个人更喜欢在单独的容器中使用scrapy和硒,并进行docker化。通过这种方式,您可以轻松地安装和抓取几乎所有都以一种形式或另一种形式包含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,}

接下来进行dockerization。建立你的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还可以检查硒容器的名称是否与我们传递给我们的scraper容器的环境变量(此处为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驱动程序。
有时,仅使用这些模块之一就可以满足您的需求。
有时您需要两个,三个或所有这些模块。
有时您需要关闭浏览器上的js。
有时,您的脚本中需要标题信息。
通常,几个月后,无需修改爬网程序,就无法永久删除同一网站,也无法永久永久删除同一网站。但是它们都可以被刮掉!有意志的地方肯定有办法。
如果您需要在未来持续不断地抓取数据,则只需抓取您需要的所有内容并将其存储在pickle中的.dat文件中即可。
只需继续搜索如何尝试使用这些模块,然后将错误复制并粘贴到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

我一直在寻找针对此问题的答案两天。许多答案将您引向不同的问题。但是,蛇形人的上述回答确实是关键。这是最短,最简单的解决方案。提醒一下,最后一个词“ var”代表变量名,因此应将其用作:

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

这应该是对蛇的答案的评论,而不是单独的答案。
Yserbius

1
很明显 但是我还没有50名代表对别人的回答发表评论。
Abd_bgc

0

我不得不在自己的某些Web抓取项目中处理相同的问题。我的处理方式是使用python请求库直接向API发出http请求,而不必加载JS。

python请求库对此非常有效,您可以通过使用inspect元素并导航至“网络”标签来查看http请求。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.