Automate interaction with websites using Mechanize

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 ),其实仔细看看里面只有 username 和 password 两项是需要填的,其他都有预设的值,在浏览器里也正是看到了这两个输入框。那么就按照平时在输入框里输入的内容填进去吧:


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

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

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


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 版的脚本:


#!/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 的工作方式更像浏览器一点:打开登录页面、填上用户名和密码、提交!当然会方便许多了! :)

Comments (6)

hsys January 09, 2008
强大~
Jack January 12, 2008
mechanize封装太多了,太智能意味着速度的不快...
pluskid January 12, 2008
@Jack:
我觉得还好啊,因为用 hpricot 分析一下 HTML 花费的时间毕竟不是瓶颈,这个年头在这个地界网速才是问题呢。 :(
Jack January 14, 2008
很多时候post一些数据并不需要去分析某个form....
liou April 23, 2008
Mechanize 是一个用于在 Ruby 脚本里将与 Web 页面的交互工作自动化的库。它会自动处理 Cookie 、重定向、Referer 之类的东西,
这个之类的东西能处理javascript的onclick事件吗?
如果不能,对我来说就没有意义
请楼主尽快回我,谢谢
pluskid April 23, 2008
@liou,
不能。当然,要看你怎么定义“处理 onClick 事件”了。如果你想让他执行 javascript 代码的话,我想现在还是办不到的。