支持AJAX的Python编程式网页浏览模块
项目描述
简介
Spynner是Python的一个有状态的编程式网页浏览器模块。它基于PyQT和WebKit。它支持JavaScript、AJAX以及WebKit能够处理的所有其他技术(Flash、SVG等)。Spynner利用了JQuery,这是一个功能强大的JavaScript库,它使得与页面交互和事件模拟变得非常简单。
使用Spynner,您可以模拟一个没有GUI的网页浏览器(尽管可以打开浏览窗口进行调试),因此它可以用于实现爬虫或验收测试工具。
请参阅使用说明: https://github.com/kiorky/spynner/tree/master/src/spynner/tests/spynner.rst 或以下预设部分
致谢
公司
贡献者
Leo Lou <https://github.com/l4u>
依赖项
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,例如: BeautifulSoup,lxml 或 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)
初始发布。 [tokland <pyarnau@gmail.com>]
项目详情
spynner-2.24.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 64bb226fe3a392dffdc12a91f243c4509d685d48b21d81c703606dadd9bce30f |
|
MD5 | 81e5933ea31aa31c342f359686c4da9a |
|
BLAKE2b-256 | 6ed9de34a1d6351263fa40d228bc71a4a043012e0a1336204ae21c3272c3c7b8 |