跳转到主要内容

支持AJAX的Python编程式网页浏览模块

项目描述

简介

https://secure.travis-ci.org/makinacorpus/spynner.png

Spynner是Python的一个有状态的编程式网页浏览器模块。它基于PyQTWebKit。它支持JavaScript、AJAX以及WebKit能够处理的所有其他技术(Flash、SVG等)。Spynner利用了JQuery,这是一个功能强大的JavaScript库,它使得与页面交互和事件模拟变得非常简单。

使用Spynner,您可以模拟一个没有GUI的网页浏览器(尽管可以打开浏览窗口进行调试),因此它可以用于实现爬虫或验收测试工具。

请参阅使用说明: https://github.com/kiorky/spynner/tree/master/src/spynner/tests/spynner.rst 或以下预设部分

致谢

公司

makinacom

作者

贡献者

依赖项

  • Python >=26

  • PyQt > 443

  • Libxml2 / Libxslt 库和 lxml 的包含文件

  • autopy,它需要 Linux 上的 xtst 库和头文件(即 Xtest)

反馈

打开一个 问题 报告错误或请求新功能。其他评论和建议可以直接发送给 作者

安装

  • 通过常规 easy_install / buildout

    easy_install spynner

    (在 Windows 上,您可能需要通过其安装程序安装 autopy,链接为 https://pypi.python.org/pypi/autopy/

  • 最新的版本托管在 github 上

    git clone https://github.com/kiorky/spynner.git
    cd spynner
    python setup.py install

在不使用X11的情况下运行Spynner

  • Spynner 需要一个 X11 服务器才能运行。如果您在没有 X11 的服务器上运行它,您必须安装虚拟的 Xvfb 服务器。Debian 用户可以使用小包装(xvfb-run)。如果您不使用 Debian,您可以在此下载它:http://www.mail-archive.com/debian-x@lists.debian.org/msg69632/x-run

    xvfb-run python myscript_using_spynner.py
  • 您还可以使用 tightvnc,这是当前维护者 [kiorky] 的解决方案。

初始化浏览器

要在那里有一个浏览器的主概念

>>> import spynner, os, sys
>>> def print_contents(browser, dest='~/.browser.html'):
...     """Print the browser contents somewhere for you to see its context
...     in doctest pdb, type print_contents(browser) and that's it, open firefox
...     with file://~/browser.html."""
...     import os
...     open(os.path.expanduser(dest), 'w').write(browser.contents)
>>> import time
>>> from StringIO import StringIO
>>> debug_stream = StringIO()
>>> bp = os.path.dirname(spynner.tests.__file__)

浏览器

>>> browser = spynner.Browser(debug_level=spynner.DEBUG, debug_stream=debug_stream)

当一切都完成时

>>> browser.close()
>>> def run_debug(callback, *args, **kwargs): # ** *
...     pos = debug_stream.pos
...     ret = callback(*args, **kwargs)
...     show_debug(pos)
...     return ret


>>> def show_debug(pos=None):
...     if not pos: print debug_stream.getvalue()
...     else:
...         pnow = debug_stream.pos
...         debug_stream.seek(pos)
...         print debug_stream.read()
...         debug_stream.seek(pnow)

调试

Spynner 使用 webkit,它是相对底层的,不要犹豫激活详细日志。有时您可能想看看发生了什么

>>> browser = spynner.Browser(debug_level=spynner.DEBUG, debug_stream=debug_stream)

或在初始化后

>>> browser.debug_level = spynner.DEBUG

在存储库中查看更多示例: https://github.com/kiorky/spynner/tree/master/examples

显示spynner窗口

也许,您还希望看到浏览器正在做什么的操作输出,只需使用这个即可

>>> browser.show()

您可以使用以下方式隐藏 webview

thebrowser.hide()

运行JavaScript

简单使用

>>> ret = browser.runjs('console.log("foobar")')

使用spynner浏览

一个基本但复杂的示例,单词参考具有资源加载,可能会失败,因此我们等待内容到达。

如果网站很好,我们可以简单地使用

>>> ret = debug_stream.read()
>>> browser.load(bp+"/html_controls.html")
True

此方法在超时抛出异常,并可以自定义默认的 30 秒超时。

但在这里,我们的目标可能会随机失败。相反,我们将加载并等待 DOM 中的某些内容到达以继续。我们等待 html 中有 'aaa',因此以 1 秒间隔无限尝试

>>> def wait_load(br):
...     return  'aaa' in browser.html

击错 URL,哎呀,你在一个无限循环中!

>>> browser.load(bp+"html_controls.html", 1, wait_callback=wait_load)

content loaded, waiting for content to mach the callback
content loaded, waiting for content to mach the callback
content loaded, waiting for content to mach the callback
content loaded, waiting for content to mach the callback
content loaded, waiting for content to mach the callback
content loaded, waiting for content to mach the callback
content loaded, waiting for content to mach the callback
content loaded, waiting for content to mach the callback
content loaded, waiting for content to mach the callback
content loaded, waiting for content to mach the callback
content loaded, waiting for content to mach the callback
<Control-C>

击错 URL,哎呀,你在一个无限循环中,除非你戴避孕套并设置尝试次数!它将抛出异常,但停止

>>> ret = debug_stream.read()

Traceback (most recent call last):
  ...
SpynnerTimeout: SPYNNER waitload: Timeout reached: 2 retries for 1s delay.

结束游戏,转到真实的目标

>>> ret = browser.load(bp+"/html_controls.html", 1, wait_callback=wait_load)
>>> [a for a in debug_stream.getvalue().splitlines() if 'SPYNNER waitload' in a][-1]
'SPYNNER waitload: The callback found what it was waiting for in its contents!'

与控件交互

  • 查看实现文档字符串或示例!

  • 您有三个级别的控制

    • webkit 方法(推荐给我们,如 wk_fill_*、wk_click_*),它们基于 jQuery。fill_* 和 click_* 方法

    • 经典方法(fill、click_*)现在是 wk_* 方法的包装器。

    • 使用 QT 原始事件的低级别(目前不是很好用)。至少,您可以移动鼠标和发送键,但它是逐案编程。

设置

>>> browser.close()
>>> del browser

使用单选输入

>>> browser = spynner.Browser(debug_level=spynner.DEBUG, debug_stream=debug_stream)
>>> ret = browser.load(bp+'/html_controls.html', 1, wait_callback=wait_load)

使用jQuery

>>> browser.load_jquery(True)

>>> browser.radio('#radiomea')

 >>> ret = run_debug(browser.runjs, '$("input[name=radiome]").each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.val()+" "+je.attr("checked"));});')
 Run Javascript code: $("input[name=radiome]").each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.val()+" "+je.attr("checked"));});
 Javascript console (:1): radiomea a true
 Javascript console (:1): radiomeb b false
 Javascript console (:1): radiomec c false
 <BLANKLINE>
 >>> browser.radio('#radiomeb')
 >>> ret = run_debug(browser.runjs, '$("input[name=radiome]").each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.val()+" "+je.attr("checked"));});')
 Run Javascript code: $("input[name=radiome]").each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.val()+" "+je.attr("checked"));});
 Javascript console (:1): radiomea a false
 Javascript console (:1): radiomeb b true
 Javascript console (:1): radiomec c false
 <BLANKLINE>

使用WebKit原生方法

在内部,我们使用 this.evaluateJavaScript(‘this.value = xxx’)

>>> browser.wk_radio('#radiomea')
>>> browser.load_jquery(True)
>>> ret = run_debug(browser.runjs, '$("input[name=radiome]").each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.val()+" "+je.attr("checked"));});')
Run Javascript code: $("input[name=radiome]").each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.val()+" "+je.attr("checked"));});
Javascript console (:1): radiomea a true
Javascript console (:1): radiomeb b false
Javascript console (:1): radiomec c false
<BLANKLINE>

使用复选输入

使用WebKit原生方法

>>> browser.close()
>>> browser = spynner.Browser(debug_level=spynner.DEBUG, debug_stream=debug_stream)
>>> ret = browser.load(bp+'/html_controls.html', 1, wait_callback=wait_load)
>>> ret = browser.load_jquery(True)

在内部,我们使用 this.evaluateJavaScript(‘this.value = xxx’)

>>> browser.wk_check('#checkmea')
>>> ret = run_debug(browser.runjs, '$($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});')
Run Javascript code: $($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});
Javascript console (:1): checkmea true
Javascript console (:1): checkmeb false
Javascript console (:1): checkmec false
<BLANKLINE>
>>> browser.wk_check(['#checkmeb', '#checkmec'])
>>> ret = run_debug(browser.runjs, '$($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});')
Run Javascript code: $($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});
Javascript console (:1): checkmea true
Javascript console (:1): checkmeb true
Javascript console (:1): checkmec true
<BLANKLINE>
>>> browser.wk_uncheck(['#checkmeb', '#checkmec'])
>>> ret = run_debug(browser.runjs, '$($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});')
Run Javascript code: $($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});
Javascript console (:1): checkmea true
Javascript console (:1): checkmeb false
Javascript console (:1): checkmec false
<BLANKLINE>
>>> browser.wk_uncheck(['#checkmea'])
>>> ret = run_debug(browser.runjs, '$($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});')
Run Javascript code: $($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});
Javascript console (:1): checkmea false
Javascript console (:1): checkmeb false
Javascript console (:1): checkmec false
<BLANKLINE>

使用jQuery

>>> browser.load(bp+'/html_controls.html', 1, wait_callback=wait_load)
>>> browser.load_jquery(True)

在内部,我们使用 $(sel).attr(‘checked’, ‘checked’)

>>> browser.check('#checkmea')
>>> ret = run_debug(browser.runjs, '$($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});')
Run Javascript code: $($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});
Javascript console (:1): checkmea true
Javascript console (:1): checkmeb false
Javascript console (:1): checkmec false
<BLANKLINE>
>>> browser.check(['#checkmeb', '#checkmec'])
>>> ret = run_debug(browser.runjs, '$($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});')
Run Javascript code: $($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});
Javascript console (:1): checkmea true
Javascript console (:1): checkmeb true
Javascript console (:1): checkmec true
<BLANKLINE>
>>> browser.uncheck(['#checkmeb', '#checkmec'])
>>> ret = run_debug(browser.runjs, '$($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});')
Run Javascript code: $($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});
Javascript console (:1): checkmea true
Javascript console (:1): checkmeb false
Javascript console (:1): checkmec false
<BLANKLINE>
>>> browser.uncheck(['#checkmea'])
>>> ret = run_debug(browser.runjs, '$($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});')
Run Javascript code: $($("input[name=checkme]")).each(function(i, e){je=$(e);console.log(je.attr("id")+" "+je.attr("checked"));});
Javascript console (:1): checkmea false
Javascript console (:1): checkmeb false
Javascript console (:1): checkmec false
<BLANKLINE>

使用选择输入

使用WebKit原生方法

>>> ret = browser.load(bp+'/html_controls.html', 1, wait_callback=wait_load)
>>> ret = browser.load_jquery(True)

在内部,我们使用 this.evaluateJavaScript(‘this.value = xxx’)

>>> browser.wk_select('#sel', 'aa')
>>> browser.runjs('$("#sel").val();').toString()
PyQt4.QtCore.QString(u'aa')
>>> browser.wk_select('#sel', 'bb')
>>> browser.runjs('$("#sel").val();').toString()
PyQt4.QtCore.QString(u'bb')
>>> browser.wk_select('#sel', 'dd')
>>> browser.runjs('$("#sel").val();').toString()
PyQt4.QtCore.QString(u'dd')

如果不是多选,则取最后一个

>>> browser.wk_select('#sel', ['aa', 'bb', 'dd'])
>>> browser.runjs('$("#sel").val();').toString()
PyQt4.QtCore.QString(u'dd')

如果是多选,则取所有

>>> browser.wk_select('#msel', ['maa', 'mbb', 'mdd'])
>>> ret = run_debug(browser.runjs, '$($("#msel option")).each(function(i, e){je=$(e);console.log(je.attr("name")+" "+je.attr("selected"));});')
Run Javascript code: $($("#msel option")).each(function(i, e){je=$(e);console.log(je.attr("name")+" "+je.attr("selected"));});
Javascript console (:1): maaa true
Javascript console (:1): mbbb true
Javascript console (:1): mccc false
Javascript console (:1): mddd true
<BLANKLINE>

使用jQuery

>>> browser.load(bp+'/html_controls.html', 1, wait_callback=wait_load)
>>> browser.load_jquery(True)

在内部,我们使用 $(sel).attr(“selected”, “selected”)

>>> browser.select('#sel option[name="bbb"]')
>>> pos = debug_stream.pos
>>> ret = run_debug(browser.runjs, '$($("#sel option")).each(function(i, e){je=$(e);console.log(je.attr("name")+" "+je.attr("selected"));});')
Run Javascript code: $($("#sel option")).each(function(i, e){je=$(e);console.log(je.attr("name")+" "+je.attr("selected"));});
Javascript console (:1): aaa false
Javascript console (:1): bbb true
Javascript console (:1): ccc false
Javascript console (:1): ddd false
<BLANKLINE>

使用具有多个参数的 select,它还可以不取消选择已选值(默认为删除)

>>> browser.select('#asel option[name="bbb"]', remove=False)
>>> ret = run_debug(browser.runjs, '$($("#asel option")).each(function(i, e){je=$(e);console.log(je.attr("name")+" "+je.attr("selected"));});')
Run Javascript code: $($("#asel option")).each(function(i, e){je=$(e);console.log(je.attr("name")+" "+je.attr("selected"));});
Javascript console (:1): aaa false
Javascript console (:1): bbb true
Javascript console (:1): ccc true
Javascript console (:1): ddd false
<BLANKLINE>
>>> browser.select('#asel option[name="bbb"]', remove=True)
>>> ret = run_debug(browser.runjs, '$($("#asel option")).each(function(i, e){je=$(e);console.log(je.attr("name")+" "+je.attr("selected"));});')
Run Javascript code: $($("#asel option")).each(function(i, e){je=$(e);console.log(je.attr("name")+" "+je.attr("selected"));});
Javascript console (:1): aaa false
Javascript console (:1): bbb true
Javascript console (:1): ccc false
Javascript console (:1): ddd false
<BLANKLINE>

如果是多选,则取所有

>>> browser.select(['#msel option[name="mbbb"]', '#msel option[name="mddd"]'])
>>> ret = run_debug(browser.runjs, '$($("#msel option")).each(function(i, e){je=$(e);console.log(je.attr("name")+" "+je.attr("selected"));});')
Run Javascript code: $($("#msel option")).each(function(i, e){je=$(e);console.log(je.attr("name")+" "+je.attr("selected"));});
Javascript console (:1): maaa false
Javascript console (:1): mbbb true
Javascript console (:1): mccc false
Javascript console (:1): mddd true
<BLANKLINE>

使用文本输入

使用WebKit原生方法

在内部,我们使用 this.evaluateJavaScript(‘this.value = xxx’)

>>> browser.wk_fill('input[name=w]', 'bar')

使用jQuery

在内部,我们使用 jQuery(selector).val(xxx)

>>> browser.fill('input[name="w"]', 'foo')
>>> ret = run_debug(browser.fill, 'input[name="w"]', 'foo')
Run Javascript code: $('input[name="w"]').val('foo')
<BLANKLINE>

jQuery笔记

Spynner 使用 jQuery 使 JavaScript 接口更容易使用。默认情况下,向每个加载的页面注入了两个模块

  • jQuery 核心库 除了其他功能外,还添加了强大的 jQuery 选择器,这些选择器被一些 Spynner 方法内部使用。当然,您也可以在向页面注入自己的代码时使用 jQuery。

  • [已过时,自行承担风险,未经维护,无错误修复] 模拟 jQuery 插件:使得模拟鼠标和键盘事件成为可能(目前 Spynner 只在 _click_ 动作中使用它)。查看库代码以了解您能触发哪些类型的事件。

由于 jQuery 已经被包含在许多主要网站上,因此如果目标网站已经加载了 JavaScript,我们就不能注入。

手动加载jQuery

>>> time.sleep(3)
>>> browser.close()
>>> browser = spynner.Browser(debug_level=spynner.DEBUG, debug_stream=debug_stream)
>>> browser.show()
>>> ret = run_debug(browser.runjs,"console.log(typeof(jQuery));")
Run Javascript code: console.log(typeof(jQuery));
Javascript console (:1): undefined
<BLANKLINE>
哎呀,我们没包含 jQuery!

正在加载它

>>> ret = browser.load_jquery(force=True)
>>> ret = run_debug(browser.runjs, "console.log(typeof(jQuery));")
Run Javascript code: console.log(typeof(jQuery));
Javascript console (:1): function
<BLANKLINE>

烹饪你的汤:解析HTML

您可以使用您喜欢的解析库来解析网页的 HTML,例如: BeautifulSouplxml 或 lxml 等。由于我们已经使用了 Jquery 进行 JavaScript,因此与 pyquery(其 Python 对应物)一起工作似乎很自然。

>>> import pyquery
>>> ret = browser.load(bp+'/html_controls.html')
>>> d = pyquery.PyQuery(browser.html)
>>> aaa = d.make_links_absolute("http://foo")[0]
>>> [dict(a.items())['href'] for a in  d.root.xpath('//a')]
['http://foo/foo', 'http://foo/a/foo', 'http://foo/../b/foo', 'http://foo/c/foo', 'http://foo/d/foo']

HTTP头部

您可以在构造时或通过 load 方法发送一个包含 http 头部的列表。

头部格式为

  • (['User-Agent', 'foobar'])

SSL支持

您有两个关键字参数可以指定

  • 要使用的支持加密列表(参见 QtSsl)

  • 要使用的协议(sslv2,tlsv1,sslv3)

鼠标

您可以在 CSS 选择器上移动

br.move_mouse('.myclass', [offsetx=0, offsety=0])

代理支持

Spynner 支持 Qt 支持的所有代理(http(s),socks5 & ftp)

请参阅示例目录中的 examples/proxy.py

基本上使用

br.set_proxy('foo:3128')
br.set_proxy('http://foo:3128')
br.set_proxy('http://user:suserpassword@foo:3128')
br.set_proxy('https://user:suserpassword@foo:3128')
br.set_proxy('socks5://user:suserpassword@foo:3128')
br.set_proxy('httpcaching://user:suserpassword@foo:3128')
br.set_proxy('ftpcaching://user:suserpassword@foo:3128')

您也可以在 download 方法中使用代理。请注意,它将默认使用通过之前的 br.set_proxy 调设置的代理

br.download('http://superfile', proxy_url='foo:3128')

变更日志

2.24 (2019-04-20)

  • 支持

2.23 (2019-03-26)

  • 支持

2.18 (2014-07-19)

  • 更改日志修复

2.17 (2014-07-19)

  • py3 支持

2.16 (2014-04-25)

  • 修复下载中的错误,退出时读取可能未完成

2.15 (2013-07-16)

  • 修复 #46

2.14 (2013-06-05)

  • cookie jar 修复 (#41)

2.13 (2013-05-17)

  • 更好的代理支持

  • Travis 设置

2.12 (2013-05-03)

  • cookie jar 修复

2.11 (2013-04-23)

  • 再次修复发布

2.10 (2013-04-22)

  • 修复发布

2.9 (2013-04-22)

  • 使用 autopy 运行本机点击

2.8 (2013-04-19)

  • 添加了一个辅助函数,以更容易地移动鼠标

2.7 (2013-04-17)

  • 更好的 SSL 支持

  • 更好的 HTTP 头部支持

  • pyside 支持

  • 更好的 cookie 支持

2.6 (2013-03-07)

  • 修复 #17:下载超时

2.5 (2013-03-06)

  • 修复 #25:新的 sslErrors 信号 API

2.4 (2012-09-28)

  • 示例 google 修复

2.3 (2012-09-28)

  • 文档

2.2 (2012-09-20)

  • 修复了由于 Yusumishi 的报告而未激活 jQuery 兼容模式的问题,感谢 Yusumishi(yusumishi@gmail.com)。

2.1 (2012-08-30)

  • 适当的发布

2.0 (2012-08-05)

  • 为合理的初始化和 API 清理创建新默认值,现在

    • 我们将模拟的函数重映射到 wk_* 上

    • 我们在 src/spynner/tests/spynner.rst 中添加了广泛的文档

    • 我们默认不嵌入 jQuery

    • 我们不自动嵌入 jQuery 的 simulate 插件,因为它已被完全弃用

1.11 (2012-08-04)

  • 适当的发布

1.10 (2011-06-07)

  • 添加 wk_check/_unckeck 方法

1.9 (2011-05-29)

  • 重新工作 JavaScript 加载 [kiorky]

  • 在原生事件方面进行一些尝试 [kiorky]

  • 修复目录问题 [kiorky]

  • 添加样本 [kiorky]

  • 修复下载 cookiesjar 释放问题 [kiorky <kiorky@cryptelium.net>]

  • 允许跟踪下载以供进一步重用 [kiorky <kiorky@cryptelium.net>]

  • 通过在响应对象中查找文件名来生成文件名。[kiorky <kiorky@cryptelium.net>]

    • 添加发送原始键盘键的方法

    • 发送原始Qt鼠标点击

    • 使用Qtwebkit原生JS点击元素并填充值

    • 一些等待内容的辅助工具

    [kiorky]

  • 添加下载文件跟踪器 [kiorky]

0.0.3 (2009-08-01)

  • 点击不等待页面加载

  • 使用QtNetwork基础设施下载文件

  • 在Browser类中公开webkit对象

  • 将jQuery更改为 _jQuery

  • HTTP认证

  • Javascript confirm和prompt的回调

  • 属性:url, html, soup

  • 更好的docstrings(使用epydoc)

  • 实现图像快照

  • 实现URL过滤器

  • 实现cookie设置 [tokland <pyarnau@gmail.com>]

0.0.2 (2009-07-27)

  • 使用browser.html而不是browser.get_html

  • 修复setup.py以使其兼容Win32

  • 添加URL过滤机制(带回调)

  • 使用类方法而不是在Browser.__init__中增加负担

  • 实例变量用于忽略SSL证书错误

  • 开始使用epydoc格式进行API文档

  • 添加create_webview/destroy_webview以进行GUI调试 [tokland <pyarnau@gmail.com>]

0.0.1 (2009-07-25)

项目详情


下载文件

下载适用于您平台的应用程序。如果您不确定选择哪个,请了解有关安装包的更多信息。

源分发

spynner-2.24.tar.gz (136.7 kB 查看哈希值)

上传时间

由以下赞助