Ruby 关于网页抓取的学习历程

wudixiaotie · 2014年08月21日 · 最后由 qq19621678 回复于 2019年04月03日 · 10250 次阅读

做网页抓取信息,其实非常非常麻烦,但是不难,最开始我也想自己弄个 gem,用 machanize,可是发现这东西真的不是特别方便,js 过多的网页要想抓取就要分析 js 文件,尤其是编译过的 js,解压后找到 ajax 请求的地址,说实话,人家主站小改以下整个程序就有可能跑不通了。

已京东的手机搜索为例子做个小程序爬了一下,分析才发现,一个页面 60 个商品,前 30 个是跟着页面过来的,后 30 个是去另外的地址通过 ajax 拿到的,而且每个商品的价格也是从另外的地方 ajax 得到的,不能说这么设计页面不好,能想到当初设计师的设计思路大概是,为了加快页面加载速度只加载一半的商品,等用户浏览到底部的时候再载入剩下的商品,价格从 ajax 请求得到,可以随时做活动打折而不用修改当前展示页面的代码。可是这可苦了作爬虫的人了。

最后发现了一个东西 casperjs,这个东西是基于 phantomjs 的,作用是用 js 代码不通过浏览器而模拟用户行为,主要用于前端自动化脚本测试(初始目的?)。用了这个其实就用不上 ruby 了。不过有了这东西就不会出现上述的情况了,拿来一个网站,不用分析 js,分析页面加载顺序。直接操作就行了,然后把操作的动作翻译成 js。希望多一些人研究这个东西,说实话,现在我有问题了都不知道问谁,大家一起进步把。。。。

正如 #13 楼 @lanyatou 所说的,这就是我后来不用 mechanize 的原因,如我最开始所说,要爬的网站一变,就要重新分析网页,重写代码。。。。实在是个噩梦,尤其是要同时爬很多网站的这种情况,永远在改以前的代码。另外关于 js 速度没有 mechanize 速度快的问题,我们可以有很多办法解决这个问题,但是程序员的时间才是最宝贵的而不是机器的时间。。。

结果我发现我错了,还是 mechanize 好。。。。。。。。。。。。

就是没有界面的浏览器,定位跟 watir 一样自动化测试,就是少了个界面。拿来采集数据那速度和内存实在感人,很多时候只作为最后的手段

说到底还是在遇到 js 的时候跑一个本地解释器,然后抓数据,我用 watir+phantomjs+nokogiri 试过,速度简直不要太慢

除非是遇到了数据非抓不可的情况,不然真没必要

现在很多网站大量的用 Ajax,网页抓取真是越来越难混了...

顶啊,今年搞过微博关键词搜索抓取,不少坑啊。

ruby 的相关 gem 有 curb(curl) 适合抓取 json 通信类数据,mechanize 适合抓取少 js 和 iframe 类页面,watir 貌似只能用来做测试,直接模拟浏览器,但是速度可想而知。phantomjs 据说很不错,没用过。

phantomjs 不错,还有个适用于 capybara 的 driver,叫 poltergeist,有需要的可以研究一下

#6 楼 @lanyatou capybara 貌似用作测试不错,抓取还是有性能问题

@flowerwrong ,你说的性能问题指的是哪里?查找元素的时候还是在模拟用户操作的时候?

@flowerwrong 那直接就 node + phantomjs 算了

phantomjs 速度慢的离谱跟浏览一样,哪里有 machanize 速度快。爬虫当然是 machanize,模拟登陆抓图什么的,可以考虑 phantomjs

casperjs 最有意思的一个功能就是能对当前页面进行截图,不错的一个小功能。

@wudixiaotie 如果爬虫的目标网站需要登录,并且是那种 js popup 那种,而且在 input 筐中的 password 是经过客户端 js 加密处理的,这种情况是不是也可以用 casperjs 解决,因为发现只要目标网站的加密规则一变,自己就得重新处理解密的代码,所以,我想,能不能直接利用 casperjs 来跑目标网站的加密 js,不知道你有遇到过这种情况没有

#13 楼 @lanyatou 遇到过就是 input 输入的是点点的那种密码输入,用 casperjs 能成功登陆。。

@wudixiaotie ,有时间的话,可以看下百度指数的页面:http://index.baidu.com

这里的登录用的是非对称加密,我试着登录,发现总是会收到用户名或密码错误的提示,觉得应该是客户端加密的代码没有在 casperjs 里面执行导致的,所以不知道 casperjs 能否直接执行这些加密代码,如果能的话,就没必要自己破解了。还有个问题就是,如果我用代理的话,只要是境外的代理,必然让我输入验证码,这个验证码不知道楼主是怎么解决的,难不成每次都要请求下验证码的地址,拿到图片后人工识别并手动输入?

#15 楼 @lanyatou 指的是登录百度么???

#15 楼 @lanyatou 我试试把,没弄过百度的登录

rtesseract 这个 gems 似乎可以解决部分的验证码

@sunday35034 这个 gem 貌似不准吧

#19 楼 @lanyatou 这是我的代码:

var casper = require('casper').create({
    verbose: true,
    logLevel: "debug",
    clientScripts: ["includes/jquery-2.1.1.min.js"]
  })
  .userAgent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36");

baidu_index_url = 'http://index.baidu.com/'

casper.start(baidu_index_url, function() {
  this.evaluate(function() {
    BID.popLogin();
  });
});

casper.then(function() {
  this.evaluate(function() {
    $('input[name="userName"]').val("xxx");
    $('input[name="password"]').val("xxx");
    $('input#TANGRAM_18__submit').click();
  });
});

casper.waitForSelector("#ubarUname", function() {
  this.capture('current_page_capture.png');
}, 10000);

casper.run();

改了一下 还是不行 但是确实是登录了 跳转到一个 url 就不通了。。。。

@wudixiaotie 你在

BID.popLogin()

的后面加上

this.__utils__.echo('-----------------------')
this.__utils__.echo(this.__utils__.visible('input#TANGRAM_18__userName'))
this.__utils__.echo(this.__utils__.visible('input#TANGRAM_18__password'))
this.__utils__.echo('-----------------------')

试试,我用你的代码测试了下,发现也没面上的这连个 input 并没有显示出来。另外,这个是我的代码;

var utils = require('utils');
var casper = require('casper').create({
    verbose: true,
    logLevel: "debug"
});


casper.userAgent('Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:18.0) Gecko/20130119 Firefox/18.0')


var usr = casper.cli.get(0);

var passwd = casper.cli.get(1);


var index_url = 'http://index.baidu.com/';

casper.start(index_url);

casper.wait(1000, function(){
    this.click('li.navli a[href="javascript:BID.popLogin();"]');
    this.wait(1000,function(){
        this.thenEvaluate(function(usr,passwd){
            this.__utils__.echo('-----------------------')
            this.__utils__.echo(this.__utils__.visible('input#TANGRAM_18__userName'))
            this.__utils__.echo(this.__utils__.visible('input#TANGRAM_18__password'))
            this.__utils__.echo('-----------------------')  

            document.querySelector('#TANGRAM_18__userName').setAttribute('value', usr);
            document.querySelector('#TANGRAM_18__password').setAttribute('value', passwd);
            this.wait(1000,function(){
                this.click('#TANGRAM_18__submit');
            });
        },{ 'usr' : usr, 'passwd' : passwd });


        this.wait(1000,function(){
            this.evaluate(function(){
                if(this.__utils__.exists('#ubarUname')){
                    this.__utils__.log('-----------login success')
                }else{
                    this.__utils__.log('-----------login faild')

                    if(this.__utils__.visible('#TANGRAM_18__error')){
                        this.__utils__.log('password error ------------')
                    }else{
                        this.__utils__.log('password not error ------------')
                    }

                    if(this.__utils__.visible('#TANGRAM_18__verifyCodeImgWrapper')){
                        this.__utils__.log('need verify code ------------')
                    }else{
                        this.__utils__.log('need not  verify code ------------')
                    }
                }
            })              
        })
    });   
});

casper.run();

打印出来是

[debug] [remote] -----------login faild
[debug] [remote] password error ------------
[debug] [remote] need not  verify code ------------

从打印出来的结果看,是登陆失败的,失败的原因是密码错误。所以我想,应该是没有调用 js 的加密函数,这也就是我想问的,怎样才能执行这个加密函数,因为我不像费劲的去破解它的加密规则,想让 phantomjs 模拟 webkit 去自动执行。

#21 楼 @lanyatou 你这个 verify code 不是加密的事,是验证码。。。。你没输入验证码

#21 楼 @lanyatou 我改了一下 你再看一下,但是还是不通。。。(在原来的楼层改的)

[debug] [phantom] Navigation requested: url=https://passport.baidu.com/v2/api/?login, type=FormSubmitted, lock=true, isMainFrame=false
[debug] [phantom] Navigation requested: url=http://index.baidu.com/static/v3Jump.htm?err_no=257&callback=parent.bd__pcbs__88fo40&codeString=captchaservice326564385963637844715a746b666a38763834672f4b703573355445436d57782b335074715053416b7157644b5855425134615863686f396775542f394667564242657251796b42335839302b6b4e326e50346a67472f716d624d534c513277564f614a35686e52672f4362312f305564696a53646f525a737155314230455565786c76396d544942723946466f34642b46782b61544d6c6673786f32417a4246736d3851714a54484b516669474a6b6475776538757a594533765633337538494769384d573879694b7a696a39486d6e714e716c63776f746e7635566d3634534d75585a7a6d4569337350754e4134494c6b6d41325169496e4931587a6b6f796a3065756754784d2b59576150396d797670414f6b6c626239574f6b4b4f6c73334f637338546132506276672b704f3968705273734d524374506e3977&userName=wudixiaotie&phoneNumber=&mail=&hao123Param=&u=http://index.baidu.com/&tpl=&secstate=&gotourl=&authtoken=&loginproxy=&resetpwd=&vcodetype=e61fcWmwhK7ZyIfWHlY+iHSmF7wcgyMjJBa+ZAChAh+2PAinQDnh3OrZ7OyH6WRIJGFdHbfaE5WylnEvy5P8hJowSJs&lstr=&ltoken=&bckv=&bcsync=&bcchecksum=&bctime=&accounts=, type=Other, lock=true, isMainFrame=false

#21 楼 @lanyatou 妈的,感觉这东西该停的地方停不下来。是我没玩明白????

是我没读懂么,怎么感觉这东西登录用起来好费劲阿。。。难道就没有完全能模拟浏览器行为的东西????

@wudixiaotie ,你抓包看一下,在密码框的 keydown 或者 keyup 事件上肯定是绑了事件的,这个事件就是 ajax 请求拿到服务器的 public_key,然后再发送登录请求的时候肯定是把用户输入的密码进行 rsa 加密的了,所以我觉得我们只是利用 setAttribute('value', usr) 或者 attr('val') 肯定是不会触发这个事件的,所以在登录请求的时候肯定是密码错误的

#26 楼 @lanyatou 还是别用这种方式了,你看看用 mechanize 然后导入登录后的 cookie 这种方式呢,又简单抓取速度又快,我实在是被 casperjs 搞的精疲力尽,高潮迭起,快不行了。。。。。

#26 楼 @lanyatou 我是这么写的:

require mechanize
baidu_agent = Mechanize.new
baidu_agent.cookie_jar.load_cookiestxt("cookies/baidu.cookies")
url = 'http://www.baidu.com'
home_page = baidu_agent.get(url)

结果还不是登录的状态,你帮我看看。

#26 楼 @lanyatou cookie 我是这么导出来的:

document.cookie

@wudixiaotie Mechanize 能够自动管理 cookie,不需要你 load 一个文件的,之所以用 casperjs 是因为它能够抓取动态内容,mechanize 在这方面还没法做到

#30 楼 @lanyatou 这个东西也不好用 还是用 watir 吧,我发现这个有简单,但是就是慢点。。

现在有一款新的产品上线了叫爬一爬 https://www.pa1pa.com/,可以免费去抓取你想要的数据,我觉得挺好用的。。。

有谁会爬取或者渗透网站会员登录信息手机号和姓名。你有技术,我有需求诚心合作 Q19621678

需要 登录 后方可回复, 如果你还没有账号请 注册新账号