Automate interaction with websites using Mechanize

January 9, 2008 – 4:44 pm

rubyMechanize 是一个用于在 Ruby 脚本里将与 Web 页面的交互工作自动化的库。它会自动处理 Cookie 、重定向、Referer 之类的东西,使用起来非常方便。我就用我今天写的一个小脚本作为例子来介绍一下 Mechanize 吧!

其实我是在寻找 Ruby 里面处理 Cookie 的库的时候找到它的。学校的网络以前是要先通过一个 Web 页面登录才能上网的(可恶的电信!),早就有前辈们写了上网登录脚本,方便使用(事实上在 Linux 下想要使用学校提供的 201+ 卡方式访问校外网的话,只有用登陆脚本了,电信提供的 IE 插件根本没法用)。可是脚本是用 perl 写的,我倒是不想为了上网自动登录大费周章地去装一个 Perl (在 Windows 下),不过倒是装了 Ruby ,便想自己写一个吧。

看了那个 Perl 脚本,又抓了写包看了看,好像挺麻烦的,有重定向、还要处理 Cookie ,Ruby 标准库 Net::HTTP 似乎没有提供 Cookie 的解决方案,也不想手工处理,便去网上搜索,一下子就找到了 Mechanize 这个库,这难道不正是我要找的那个东西吗?!

粗略地扫描了下他的 README ,便开始在 IRB 里做实验,先 new 一个 agent 出来:

require 'rubygems'
require 'mechanize'
 
agent = WWW::Mechanize.new

打开登录页面:

page = agent.get('http://61.175.164.14:8880/webLogin.jsp')

可是出错了:

WWW::Mechanize::ResponseCodeError: 403 => Net::HTTPForbidden

唉!这都是那可恶的电信,居然还判断 User-Agent 的,用 Firefox 都还好,用 Opera 就不能登录,也不知道他们是怎么想的。且不管那么多,既然有限制,我们就欺骗一下好了。Mechanize 有许多内置的 User-Agent 可以供我们选(参见 WWW::Mechanize::AGENT_ALIASES ):

agent.user_agent_alias = 'Windows Mozilla'
page = agent.get('http://61.175.164.14:8880/webLogin.jsp')

这下页面获取到手了,从 IRB 的输出就可以看到里面有一个简单的 form ,只有一个字段 LocalIP ,不管三七二十一,用本地 ip 填上这个字段,然后提交回去:

form = page.form('mainform')
form.LocalIP = get_local_ip
page = agent.submit(form)

这下得到了一个好长的页面,里面有一个超级大的 form (还是叫做 mainform ),其实仔细看看里面只有 usernamepassword 两项是需要填的,其他都有预设的值,在浏览器里也正是看到了这两个输入框。那么就按照平时在输入框里输入的内容填进去吧:

form = page.form('mainform')
form.username = '800387278211@zheda'
form.password = 'my password'
page = agent.submit(form)

再返回的页面里面已经没有 form 了,出问题了?不是!打开浏览器,发现已经可以上网了! :D

Mechanize 确实很好用啊,比传统的用于处理 web 页面的脚本抽象了更多的东西,让我们可以更加专注于需要关注的东西,而不用去处理细枝末节了。下面分别是用 Mechanize 做的 Ruby 脚本和原本的 Perl 脚本,从长度就可以看出来差别了,而且 Ruby 脚本应该是更易懂的 :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require 'rubygems'
require 'mechanize'
 
def get_local_ip
  IO.popen("ipconfig").read =~ /IP Address[. ]+: ([0-9.]+)/
  return $1
end
 
agent = WWW::Mechanize.new
agent.user_agent_alias = 'Windows Mozilla'
 
page = agent.get('http://61.175.164.14:8880/webLogin.jsp')
 
exit if page.forms.empty?       # already logged in
 
form = page.form('mainform')
form.LocalIP = get_local_ip
page = agent.submit(form)
 
form = page.form('mainform')
form.username = '800387278211@zheda'
form.password = 'my password'
page = agent.submit(form)

然后是 Perl 版的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#!/usr/bin/perl
 
use strict;
use warnings;
use LWP::UserAgent;
 
my $cmd = "/sbin/ifconfig | sed -ne \'s/.*inet addr:\\([^ ]*\\).*/\\1/p\'";
chomp(my @myaddr = `$cmd`);
my $ip = $myaddr[0];
 
my $url='http://61.175.164.14:8880/webLogin.jsp';
 
my $agent=new LWP::UserAgent(
        keep_alive => 1,
        timeout => 30,
        agent => 'mozilla/4.0',
        cookie_jar => {file => "lwpcookies.txt", autosave => 1}
        );
my $request=new HTTP::Request('GET' => $url);
my $response=$agent->request($request);
 
$response->is_success() or die "Maybe dx server down\n";
print "Got key success, processing...\n";
 
my %form1 = ('LocalIP'=>$ip);
$response=$agent->post($url, \%form1);
 
$response->is_success() or die "Something I have not encounter happened :(\n";
print "Get main page success, processing...\n";
 
my %form2 = (
	'username' => "$ARGV[0]\@zheda",
	'password' => "$ARGV[1]",
	'cookiedate' => '3650',
	'consumeright' => '0',
        'separatecard' => '0',
        'localIP' => $ip,
        'needActiveX' => '1',
        'isPNP' => '0',
        'httpIP' => $ip,
        'isProxy' => '0',
        'clienttype' => '1',
        'ip' => $ip,
        'id' => '',
        'languagetype' => '0',
        'operatingSystem' => '1',
        'httpIp' => $ip,
        'servicetype'  => 'portallocal'
);
 
 
$url='http://61.175.164.14:8880/secu/webLogin.jsp';
$response=$agent->post($url, \%form2);
 
$response->is_success() or die "Something I have not encounter happened :(\n";
print "Send Card infomation success, processing...\n";
 
$response->content() =~ m/key=(\d*)/;
 
$url='http://61.175.164.14:8880/myportal/loginsetparameter.jsp?issecu=1&key='.$1;
$response=$agent->get($url);
$response->is_success() or die "Get Parameter failed\n";
 
$response->is_success() or die "Something I have not encounter happened :(\n";
print "Send Card infomation success, processing...\n";
 
$response->content() =~ m/key=(\d*)/;
 
$url='http://61.175.164.14:8880/myportal/loginsetparameter.jsp?issecu=1&key='.$1
;
$response=$agent->get($url);
$response->is_success() or die "Get Parameter failed\n";
 
print "done\n";

可以看到用传统的方法的话,需要抓包分析,而且许多事情需要手工来做,比如一个 form 的内容通常需要全部填写(看 Perl 脚本里面的那个巨大的 form )。如果是使用浏览器的话,许多工作就没有了,浏览器会自动跟踪重定向,而且隐藏的 form 字段也不会显示出来让你全部填写,而 Mechanize 的工作方式更像浏览器一点:打开登录页面、填上用户名和密码、提交!当然会方便许多了! :)

  1. 6 Responses to “Automate interaction with websites using Mechanize”

  2. 强大~

    By hsys on Jan 9, 2008

  3. mechanize封装太多了,太智能意味着速度的不快…

    By Jack on Jan 12, 2008

  4. @Jack:
    我觉得还好啊,因为用 hpricot 分析一下 HTML 花费的时间毕竟不是瓶颈,这个年头在这个地界网速才是问题呢。 :(

    By pluskid on Jan 12, 2008

  5. 很多时候post一些数据并不需要去分析某个form….

    By Jack on Jan 14, 2008

  6. Mechanize 是一个用于在 Ruby 脚本里将与 Web 页面的交互工作自动化的库。它会自动处理 Cookie 、重定向、Referer 之类的东西,
    这个之类的东西能处理javascript的onclick事件吗?
    如果不能,对我来说就没有意义
    请楼主尽快回我,谢谢

    By liou on Apr 23, 2008

  7. @liou,
    不能。当然,要看你怎么定义“处理 onClick 事件”了。如果你想让他执行 javascript 代码的话,我想现在还是办不到的。

    By pluskid on Apr 23, 2008

Post a Comment