mechanize的follow_link()和back()函数问题
我遇到了一个关于使用 mechanize 跟踪链接的问题。下面是我想做的事情的一小段代码:
for link in mech.links(url_regex='/test/'):
mech.follow_link(link)
// Do some processing on that link
mech.back()
根据 mechanize 的示例,这应该可以正常工作。但是实际上并没有。尽管我调用了 .back() 方法,循环还是结束了,尽管还有更多的链接可以访问。如果我把 mech.follow_link(link) 和 mech.back() 注释掉,换成 print link.text,那么它会打印出大约 50 个链接。但是...一旦我取消对 mech.follow_link 的注释,循环就会在第一次跟踪链接后立即终止。back() 方法是有效的,因为如果我打印 mech.title(),然后调用 mech.back() 再打印一次 mech.title(),我能清楚地看到第一个标题和“返回”页面的标题。我真的很困惑,这在文档中是这样写的。我不太明白发生了什么。
3 个回答
每次访问页面时,链接的迭代器会重置为新页面上的链接。因此,你需要把它保存到一个单独的变量里,比如:links = mech.links()
,或者像Chirael提到的那样,使用links = list(mech.links())
,这样做的好处是可以用print >>sys.stderr, '# links: %d' % len(links)
来计算链接的数量。这并不是mechanize.Browser的错误,只是因为它是一个有状态的对象导致的一个副作用。
我在玩这个的时候还发现了另一个小问题,如果mech.request
一开始没有设置,就不能使用mech.back()
。如果你是用mech.set_response()
来设置原始页面内容的话,它就不会被设置。在这种情况下,你需要明确地把第一次请求设置为某个东西:mech.request = mechanize.Request('about://config')
。否则你会遇到BrowserStateError: already at start of history
的错误。
为了完整起见,如果有人是通过谷歌搜索来到这里的,像我一样,确保在mechanize.make_response
中设置头信息,至少要设置为(('content-type', 'text/html'),)
,否则mech.viewing_html
会保持为False
,这时mech.links()
会抛出BrowserStateError("not viewing HTML")
的错误。
一个比保存链接列表更简单的解决办法就是直接再创建一个浏览器对象。这就像在一个“真实”的浏览器里打开第二个标签页一样。如果你还需要登录验证,那就需要在这两个浏览器实例之间共享一个“cookie罐子”:
import mechanize
import cookielib
br = mechanize.Browser()
br2 = mechanize.Browser()
cj = cookielib.LWPCookieJar()
br.set_cookiejar(cj)
br2.set_cookiejar(cj)
br.open("http://yoursite.com/login")
br.select_form(nr=0)
br["username"] = "..." # The hash keys are the names of the form fields
br["password"] = "..."
br.submit() # This will save the authentication cookie to the shared cookie jar!
br.open("http://yoursite.com/page-to-parse")
for link in br.links(url_regex="/link_text"):
req = br.click_link(url=link.url)
html = br2.open(req).read()
需要注意的是,你必须从第一个实例获取一个请求对象,然后用第二个实例来提交它。这就相当于在“真实”浏览器中选择“在新窗口/标签页中打开”的命令。
海盗,我同意,这种情况不应该发生。你做的几乎和文档页面 wwwsearch.sourceforge.net/mechanize/ 上说的一模一样;我试过类似的代码,结果也是在第一次循环后就停止了。
不过,我找到了一种解决办法,就是把 links() 里的链接地址保存到一个列表中,然后再从这个列表中逐个访问每个链接:
from mechanize import Browser
br = Browser()
linklist = []
br.open(your_page_here)
for link in br.links(url_regex='/test/'): linklist.append(link.url)
for url in linklist:
br.open(url)
print br.title()
虽然这个方法看起来不太好,但似乎有效。
我对 mechanize 这种 bug 问题并不太满意(还有一个问题是 mechanize 对两个提交按钮处理得不好),不过它安装起来很简单,似乎也很方便,可以离线运行(通过简单的定时任务),相比其他测试框架,比如 Selenium(seleniumhq dot org),看起来要简单很多,虽然 Selenium 看起来很不错,但设置和使用起来似乎要复杂得多。