如何在Eventlet页面抓取器中保持会话?

3 投票
3 回答
1121 浏览
提问于 2025-04-15 19:28

我正在尝试从一个需要登录的网站抓取数据(不是那种简单的HTTP认证)。我用的脚本是基于这个eventlet的例子。简单来说,

urls = ["https://mysecuresite.com/data.aspx?itemid=blah1",
     "https://mysecuresite.com/data.aspx?itemid=blah2",
     "https://mysecuresite.com/data.aspx?itemid=blah3"]

import eventlet
from eventlet.green import urllib2  

def fetch(url):
  print "opening", url
  body = urllib2.urlopen(url).read()
  print "done with", url
  return url, body

pool = eventlet.GreenPool(10)
for url, body in pool.imap(fetch, urls):
  print "got body from", url, "of length", len(body)

建立会话并不简单;我需要先加载登录页面,从登录表单中提取一些变量,然后发送一个包含认证信息和这些变量的POST请求。等会话建立好后,后面的请求就可以简单地用GET请求来完成了。

根据上面的代码,我该如何创建一个会话,让后面的请求都能使用这个会话呢?(我需要后续的请求能够并行进行)

3 个回答

0

你可以使用这个叫做mechanize的库,它可以让你更轻松地建立会话。然后,你可以使用一些不同的线程或多进程的方法,比如这个线程池的例子(这是在谷歌上搜索的第一个结果,可能有点复杂,记得看看评论)。

1

正如下面所建议的,使用 mechanize。这个工具会帮你处理一些底层的细节,比如管理 cookies。

不过,要让第三方库和 eventlet 一起工作,你需要把标准库中的 socket 和 ssl 对象换成一些底层是异步的东西。

在 eventlet 中是可以做到的,但这里面不是特别简单。 我建议使用 gevent,这样你只需要做以下操作:

from gevent import monkey; monkey.patch_all()

然后第三方库就应该能正常工作了。

这里有一个 示例

4

我不是这方面的专家,但看起来用urllib2保持会话状态的标准方法是为每个会话创建一个自定义的打开器实例。这个过程大概是这样的:

opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())

然后你可以用这个打开器进行任何需要的身份验证,所有的会话状态都会保存在这个打开器对象里。接着,你可以把这个打开器对象作为参数传递给并行请求。

这里有一个示例脚本,它可以让多个用户同时登录到secondlife.com,并为每个用户同时请求多个页面。这个网站的登录过程比较复杂,因为在第二次请求登录之前,需要先从第一次请求中获取一个CSRF令牌。因此,登录的方法会显得有些麻烦。不过,原则上对于你感兴趣的任何网站,应该都是类似的。

import eventlet
from eventlet.green import urllib2
import re

login_url = 'https://secure-web28.secondlife.com/my/account/login.php?lang=en&type=second-life-member&nextpage=/my/index.php?lang=en'

pool = eventlet.GreenPool(10)

def fetch_title(opener, url):
    match = re.search(r'<title>(.*)</title>', opener.open(url).read())
    if match:
        return match.group(1)
    else:
        return "no title"

def login(login_url, fullname, password):
    opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())
    login_page = opener.open(login_url).read()
    csrf_token = re.search(r'<input type="hidden" name="CSRFToken" value="(.*)"/>', login_page).group(1)
    username, lastname = fullname.split()
    auth = "CSRFToken=%s&form[type]=second-life-member&form[nextpage]=/my/index.php?lang=en&form[persistent]=Y&form[form_action]=Log%%20In&form[form_lang]=en&form[username]=%s&form[lastname]=%s&form[password]=%s&submit=Submit" % (
        csrf_token, username, lastname, password)
    logged_in = opener.open(login_url, auth).read()
    return opener


def login_and_fetch(login_url, fullname, password, page_urls):
    opener = login(login_url, fullname, password)
    # note that this deliberately uses the global pool
    pile = eventlet.GreenPile(pool)
    for url in page_urls:
        pile.spawn(fetch_title, opener, url)

    return pile

login_urls = [login_url] *2
usernames = [...]
passwords = [...]
page_urls = [['https://secure-web28.secondlife.com/my/account/?lang=en-US',
        'https://secure-web28.secondlife.com/my/community/events/index.php?lang=en-US']] * 2

for user_iter in pool.imap(login_and_fetch, login_urls, usernames, passwords, page_urls):
    for title in user_iter:
        print "got title", title

撰写回答