【Python爬虫5】提取JS动态网页数据

  • 动态网页示例
  • 对加载内容进行逆向工程
    • 1通过开发者工具的逆向工程
    • 2通过墨盒测试的逆向工程
      • 21搜索条件为空时
      • 22用号匹配时
      • 22用号匹配时
  • 渲染动态网页
    • 1使用WebKit渲染引擎
    • 2使用Selenium自定义渲染

现在大部分的主流网站都用JavaScript动态显示网页内容,这样使得我们之前提取技术无法正常运行。本篇将介绍两种提取基于JS动态网页的数据。

  • JavaScript逆向工程
  • 渲染JavaScript

1.动态网页示例

我们先看一个动态网页的示例。在示例网站的中,我们从http://127.0.0.1:8000/places/default/search 搜索国家名包涵A的表单。
我们根据按F12开发者工具显示的标签,用lxml模块提取数据,发现提取不到什么数据。

>>> import lxml.html
>>> from downloader import Downloader
>>> D=Downloader()
>>> html=D('http://127.0.0.1:8000/places/default/search')
Downloading: http://127.0.0.1:8000/places/default/search
>>> tree=lxml.html.fromstring(html)
>>> tree.cssselect('div#result a')
[]
>>> 

我们在浏览器右击查看网页源代码发现我们要提取的div数据是空的。

...
<div id="results">
</div>
...

这是因为F12的开发者工具是显示的标签是网页当前的状态,也就是使用JavaScript动态加载完搜索结果之后的网页。

2.对加载内容进行逆向工程

由于这些网页的数据是JS动态加载的,要想提取该数据,我们需要网页如何加载该数据的,该过程也被称为逆向工程

2.1通过开发者工具的逆向工程

我们在上节F12的开发者工具的Network发现AJAX响应一个json文件,即:http://127.0.0.1:8000/places/ajax/search.json?&search_term=A&page_size=10&page=0 。AJAX响应的返回数据是JSON格式的,因此我们可以使用Python的json模块将解析为一个字典。

>>> import json
>>> html=D('http://127.0.0.1:8000/places/ajax/search.json?&search_term=A&page_size=10&page=0')
Downloading: http://127.0.0.1:8000/places/ajax/search.json?&search_term=A&page_size=10&page=0
>>> json.loads(html)
{u'records': [
  {u'pretty_link': u'<div><a href="/places/default/view/Afghanistan-1"><img src="/places/static/images/flags/af.png" /> Afghanistan</a></div>', u'country': u'Afghanistan', u'id': 3781},
  {u'pretty_link': u'<div><a href="/places/default/view/Aland-Islands-2"><img src="/places/static/images/flags/ax.png" /> Aland Islands</a></div>', u'country': u'Aland Islands', u'id': 3782},...],
u'num_pages': 22,
u'error': u''}
>>> 

我们可以通过分页请求提取json数据存到txt文件中。分页请求会让同一个国家在多次搜索返回多次,但通过set()集合会过滤重复的元素。

# -*- coding: utf-8 -*-
import json
import string
import downloader

def main():
    template_url = 'http://127.0.0.1:8000/places/ajax/search.json?&page={}&page_size=10&search_term={}'
    countries = set()
    download = downloader.Downloader()
    for letter in string.lowercase:
        page = 0
        while True:
            html = download(template_url.format(page, letter))
            try:
                ajax = json.loads(html)
            except ValueError as e:
                print e
                ajax = None
            else:
                for record in ajax['records']:
                    countries.add(record['country'])
            page += 1
            if ajax is None or page >= ajax['num_pages']:
                break
    open('2countries2.txt', 'w').write('\n'.join(sorted(countries)))

if __name__ == '__main__':
    main()

2.2通过墨盒测试的逆向工程

在不知道源代码的情况下的测试称为墨盒测试。我们可以使用一次搜索查询就能匹配所有结果,接下来,我们将尝试使用不同字符测试这种想法是否可行。

2.2.1搜索条件为空时

>>> import json
>>> from downloader import Downloader
>>> D=Downloader()
>>> url='http://127.0.0.1:8000/places/ajax/search.json?&page_size=10&page=0&search_term='
>>> json.loads(D(url))['num_pages']
Downloading: http://127.0.0.1:8000/places/ajax/search.json?&page_size=10&page=0&search_term=
0
>>> 

搜索条件为空时,这种方法并没有奏效。

2.2.2用*号匹配时

>>> json.loads(D(url+'*'))['num_pages']
Downloading: http://127.0.0.1:8000/places/ajax/search.json?&page_size=10&page=0&search_term=*
0

*号匹配时,这种方法也没有奏效。

2.2.2用.号匹配时

>>> json.loads(D(url+'.'))['num_pages']
Downloading: http://127.0.0.1:8000/places/ajax/search.json?&page_size=10&page=0&search_term=.
26

这种方法测试成功了,看来服务器是通过正则表达式进行匹配的。在搜索界面中包含4、10、20这几种选项,其中默认值是10。我们增加显示数量进行测试。

>>> url='http://127.0.0.1:8000/places/ajax/search.json?&page_size=20&page=0&search_term='
>>> json.loads(D(url+'.'))['num_pages']
Downloading: http://127.0.0.1:8000/places/ajax/search.json?&page_size=20&page=0&search_term=.
13
>>> url='http://127.0.0.1:8000/places/ajax/search.json?&page_size=1000&page=0&search_term='
>>> json.loads(D(url+'.'))['num_pages']
Downloading: http://127.0.0.1:8000/places/ajax/search.json?&page_size=1000&page=0&search_term=.
1
>>> 

我们如下整合过完整代码。

# -*- coding: utf-8 -*-

import json
import csv
import downloader

def main():
    writer = csv.writer(open('2.2countries.csv', 'w'))
    D = downloader.Downloader()
    #html = D('http://example.webscraping.com/ajax/search.json?page=0&page_size=1000&search_term=.')
    html = D('http://127.0.0.1:8000/places/ajax/search.json?&page_size=1000&page=0&search_term=.')
    ajax = json.loads(html)
    for record in ajax['records']:
        writer.writerow([record['country']])

if __name__ == '__main__':
    main()

3.渲染动态网页

一些网站用Google Web Toolkit(GWT)工具开发的,产生的JS代码是压缩的,但可以通过JSbeautifier工具进行还原,但逆向工程效果不是很好。渲染引擎是浏览器加载网页时解析HTML、应用CSS样式并执行JS语句进行渲染显示。本节中我们使用WebKit渲染引擎,并通过Qt框架获得引擎的一个便捷Python接口,也可以用Selenium自定义渲染。

3.1使用WebKit渲染引擎

<html>
    <body>
        <div id="result"></div>
        <script>document.getElementById("result").innerText = 'Hello World';</script>
    </body>
</html>
# -*- coding: utf-8 -*-
import lxml.html
import downloader
try:
    from PySide.QtGui import *
    from PySide.QtCore import *
    from PySide.QtWebKit import *
except ImportError:
    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    from PyQt4.QtWebKit import *

def direct_download(url):
    download = downloader.Downloader()
    return download(url)

def webkit_download(url):
    app = QApplication([])
    webview = QWebView()
    loop=QEventLoop()
    webview.loadFinished.connect(loop.quit)
    webview.load(QUrl(url))
    app.exec_() # delay here until download finished
    return webview.page().mainFrame().toHtml()

def parse(html):
    tree = lxml.html.fromstring(html)
    print tree.cssselect('#result')[0].text_content()

def main():
    url = 'http://127.0.0.1:8000/places/default/dynamic'
    #url = 'http://example.webscraping.com/dynamic'
    parse(direct_download(url))
    parse(webkit_download(url))
    return
    print len(r.html)

if __name__ == '__main__':
    main()
# -*- coding: utf-8 -*-

try:
    from PySide.QtGui import QApplication
    from PySide.QtCore import QUrl, QEventLoop, QTimer
    from PySide.QtWebKit import QWebView
except ImportError:
    from PyQt4.QtGui import QApplication
    from PyQt4.QtCore import QUrl, QEventLoop, QTimer
    from PyQt4.QtWebKit import QWebView

def main():
    app = QApplication([])
    webview = QWebView()
    loop = QEventLoop()
    webview.loadFinished.connect(loop.quit)
    webview.load(QUrl('http://127.0.0.1:8000/places/default/dynamic'))
    #webview.load(QUrl('http://example.webscraping.com/search'))
    loop.exec_()

    webview.show()
    frame = webview.page().mainFrame()
    frame.findFirstElement('#search_term').setAttribute('value', '.')
    frame.findFirstElement('#page_size option:checked').setPlainText('1000')
    frame.findFirstElement('#search').evaluateJavaScript('this.click()')

    elements = None
    while not elements:
        app.processEvents()
        elements = frame.findAllElements('#results a')
    countries = [e.toPlainText().strip() for e in elements]
    print countries

if __name__ == '__main__':
    main()
# -*- coding: utf-8 -*-

import re
import csv
import time
try:
    from PySide.QtGui import QApplication
    from PySide.QtCore import QUrl, QEventLoop, QTimer
    from PySide.QtWebKit import QWebView
except ImportError:
    from PyQt4.QtGui import QApplication
    from PyQt4.QtCore import QUrl, QEventLoop, QTimer
    from PyQt4.QtWebKit import QWebView
import lxml.html

class BrowserRender(QWebView):
    def __init__(self, display=True):
        self.app = QApplication([])
        QWebView.__init__(self)
        if display:
            self.show() # show the browser

    def open(self, url, timeout=60):
        """Wait for download to complete and return result"""
        loop = QEventLoop()
        timer = QTimer()
        timer.setSingleShot(True)
        timer.timeout.connect(loop.quit)
        self.loadFinished.connect(loop.quit)
        self.load(QUrl(url))
        timer.start(timeout * 1000)
        loop.exec_() # delay here until download finished
        if timer.isActive():
            # downloaded successfully
            timer.stop()
            return self.html()
        else:
            # timed out
            print 'Request timed out:', url

    def html(self):
        """Shortcut to return the current HTML"""
        return self.page().mainFrame().toHtml()

    def find(self, pattern):
        """Find all elements that match the pattern"""
        return self.page().mainFrame().findAllElements(pattern)

    def attr(self, pattern, name, value):
        """Set attribute for matching elements"""
        for e in self.find(pattern):
            e.setAttribute(name, value)

    def text(self, pattern, value):
        """Set attribute for matching elements"""
        for e in self.find(pattern):
            e.setPlainText(value)

    def click(self, pattern):
        """Click matching elements"""
        for e in self.find(pattern):
            e.evaluateJavaScript("this.click()")

    def wait_load(self, pattern, timeout=60):
        """Wait for this pattern to be found in webpage and return matches"""
        deadline = time.time() + timeout
        while time.time() < deadline:
            self.app.processEvents()
            matches = self.find(pattern)
            if matches:
                return matches
        print 'Wait load timed out'

def main():
    br = BrowserRender()
    br.open('http://127.0.0.1:8000/places/default/dynamic')
    #br.open('http://example.webscraping.com/search')
    br.attr('#search_term', 'value', '.')
    br.text('#page_size option:checked', '1000')
    br.click('#search')

    elements = br.wait_load('#results a')
    writer = csv.writer(open('countries.csv', 'w'))
    for country in [e.toPlainText().strip() for e in elements]:
        writer.writerow([country])

if __name__ == '__main__':
    main()

3.2使用Selenium自定义渲染

from selenium import webdriver

def main():
    driver = webdriver.Firefox()
    driver.get('http://127.0.0.1:8000/places/default/dynamic')
    #driver.get('http://example.webscraping.com/search')
    driver.find_element_by_id('search_term').send_keys('.')
    driver.execute_script("document.getElementById('page_size').options[1].text = '1000'");
    driver.find_element_by_id('search').click()
    driver.implicitly_wait(30)
    links = driver.find_elements_by_css_selector('#results a')
    countries = [link.text for link in links]
    driver.close()
    print countries

if __name__ == '__main__':
    main()

Wu_Being 博客声明:本人博客欢迎转载,请标明博客原文和原链接!谢谢!
【Python爬虫系列】《【Python爬虫5】提取JS动态网页数据》http://blog.csdn.net/u014134180/article/details/55507014
Python爬虫系列的GitHub代码文件https://github.com/1040003585/WebScrapingWithPython

如果你看完这篇博文,觉得对你有帮助,并且愿意付赞助费,那么我会更有动力写下去。

时间: 2024-10-03 08:10:24

【Python爬虫5】提取JS动态网页数据的相关文章

Python爬虫:用BeautifulSoup进行NBA数据爬取

爬虫主要就是要过滤掉网页中无用的信息,抓取网页中有用的信息 一般的爬虫架构为: 在python爬虫之前先要对网页的结构知识有一定的了解,如网页的标签,网页的语言等知识,推荐去W3School: W3school链接进行了解 在进行爬虫之前还要有一些工具: 1.首先Python 的开发环境:这里我选择了python2.7,开发的IDE为了安装调试方便选择了用VS2013上的python插件,在VS上进行开发(python程序的调试与c的调试差不多较为熟悉): 2.网页源代码的查看工具:虽然每一个浏

url-使用Python抓取下一页网页数据

问题描述 使用Python抓取下一页网页数据 怎么抓取一个无论怎么跳转其url都不变的网页?通过Requests和BeautifulSoup能实现吗? http://www.szairport.com/frontapp/HbxxServlet?iscookie=C 另外其下一页的跳转指令是js写的,我该怎么通过这条指令跳转下一页,命令如下: [<a href="javascript:void(0);" onclick="page.moveNext()">

Java_爬虫,如何抓取Js动态生成数据的页面?

问题描述 很多网站是用js或Jquery 生成数据的,到后台获取到数据以后,用 document.write()或者("#id").html="" 的方式 写到页面中,这个时候用浏览器查看源码是看不到数据的.HttpClient是不行的,看网上说HtmlUnit,说  可以获取后台js加载完后的完整页面,但是我按照文章上说的 写了 ,都不好使.String url = "http://xinjinqiao.tprtc.com/admin/main/flrp

[Python爬虫] 在Windows下安装PIP+Phantomjs+Selenium

        最近准备深入学习Python相关的爬虫知识了,如果说在使用Python爬取相对正规的网页使用"urllib2 + BeautifulSoup + 正则表达式"就能搞定的话:那么动态生成的信息页面,如Ajax.JavaScript等就需要通过"Phantomjs + CasperJS + Selenium"来实现了.所以先从安装和功能介绍入门,后面在介绍一些Python相关的爬虫应用. 一. 介绍        PhantomJS        Pha

JSP动态网页制作一定要知道的基础知识

js|动态|网页 一个普通的错误是把JSP当作简化的 Java.它不是,(事实上, JSP 是简化的 servlets .)程序员通常试着没有学习要求的支持技巧而 直接学习 JSP .JSP 是一个衔接技术,并且成功地连接你需要理解的另外的技术.如果你已经知道 Java , HTML 和 javascript,这意味着 JSP 将确实是简单的. 需要成为一个成功的 JSP 程序员可以参考这个时间表.请注意下列: *忽略你已经熟悉的步骤. *训练的时间只是代表学习好足够的基础时间,这样才能转移到下

JSP动态网页入门:表单输入例子

js|动态|网页 我们将创建一个Web页面,它有一个输入表单,用户可以输入一个股票代号以获得出当前股票价格(有20分钟延迟).如果输入有误,则显示错误提示页面. quote.jsp 首先,用以下代码创建quote.jsp页面并将其保存在JSWDK安装目录下的Web 目录中.大多数的页面是含JSP代码的标准HTML.第六行是一个页面指令,表明将把所有错误发送到"errorPage.jsp"文中.第13到15行是一个脚本段,主要说明仅当有"symbol"参数时才显示表格

新手入门:了解JSP动态网页制作技术

js|动态|网页 JavaServer Pages (JSP) 是一种结合了跨平台与跨网站服务器机制,以 Java为主的技术来产生动态网页. JSP与微软的ASP(Active Server Pages )相容,但它是使用类似HTML的标签以及Java代码片段而不是VBScript.当你所使用的网站服务器没有提供本地ASP支援,也就是Apache或Netscape服务器时,你可以考虑使用JSP.虽然你可以取得这些服务器的ASP附加配备程序,但价格相当昂贵,而目前Sun并没有对你所要的JSP组件收

[网络爬虫]使用node.js cheerio抓取网页数据

想要自动从网页抓一些数据或者想把一坨从什么博客上拉来的数据转成一种有结构的数据? 居然没有现成的API可以取数据?!!! !@#$@#$- 没关系 网页抓取可以解决. 什么是网页抓取? 你可能会问... 网页抓取是以编程的方式(通常不用浏览器参与)检索网页的内容并从中提取数据的过程.   本文,小编会给大家展示一套强大的抓取工具,可以快速的对网面进行抓取,而且容易上手,它是由javascript 和node.js实现的.   最近我需要爬一些大数量(谦虚的说)的页面,然后分析它们从中找到一些规律

Python爬虫实战:股票数据定向爬虫

功能简介 目标: 获取上交所和深交所所有股票的名称和交易信息. 输出: 保存到文件中. 技术路线: requests-bs4–re 语言:python3.5 说明 网站选择原则: 股票信息静态存在于html页面中,非js代码生成,没有Robbts协议限制. 选取方法: 打开网页,查看源代码,搜索网页的股票价格数据是否存在于源代码中. 如打开新浪股票网址:链接描述(http://finance.sina.com.cn/realstock/company/sz000877/nc.shtml),如下图