首先感谢COS论坛同意我在这篇水文中用RCurl做一些简单的演示,但由这些功能的延展而给COS论坛造成的任何损失或破坏,请各位自己负责。



一、RCurl是什么

混迹于各大社区,经常会看到关于浏览器之争的口水战:某某浏览器的市场份额如何如何,某某浏览器的速度如何如何,某某浏览器支持的功能多么强大等等。各个网友也根据自己的喜好,将自身归档于某某浏览器阵营,以此找些心灵的归属。估计类似的口水之争将永远的进行下去(是啊,不然闲着干什么呢?)。如果换个角度看这些争论,也正反应出浏览器在大家日常生活中的地位:想想每天坐在电脑前,用的最多的软件是什么呢?但是提到浏览器阵营中的cURL——一款杀人放火、居家旅游必备的命令行浏览器,则普及率要不少。可它的功能绝不逊色于我们日常用的各大浏览器。RRCurl包是对cURL库—libcurl的封装。感谢Duncan Temple Lang等牛人的无私工作,我们才可以在R中运用cURL,将R和cURL这两大开源利器的优势完美的结合到一起。



二、用RCurl浏览网页

想想我们平时绝大部分时间是怎么用浏览器的?第一步:打开自己钟爱的那款浏览器;第二部:输入某个网址,如http://cos.name/;第三部:回车;第四步:拖拖鼠标,看自己想看的东西;第五步:点进某个链接,接着看。在关注呈现的信息的时候,大多数人都不大会去关心上述的5步(或者更多步)中浏览器(客户端)和网站(服务器端)是如何工作的。其实客户端和服务器端一直在保持联系:告诉对方想干什么,是否同意等等内容?比如我们浏览http://cos.name/时,浏览器给服务器端提交了如下的一些内容:

GET /HTTP/1.1

Host:cos.name

User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.6)

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language:en-us

Accept-Encoding:gzip,deflate

Accept-Charset:GB2312,utf-8;q=0.7,*;q=0.7

Keep-Alive:300

Connection:keep-alive

通过上面的头信息,浏览器除了告诉COS服务器想浏览哪些内容,还告诉对方用了什么浏览器、想要什么样的数据格式、用什么协议/方法接收等非常细节的内容。COS服务器收到这些请求后,同样会提供一个物品清单:

HTTP/1.x200 OK

Date:Fri, 01 Jan 2010 13:11:20 GMT

Server:Apache/2.2.14 (Unix) X-Powered-By: PHP/5.2.11

X-Pingback:http://cos.name/xmlrpc.php

Vary:Accept-Encoding

Content-Encoding:gzip

Content-Length:13973

Keep-Alive:timeout=10, max=30

Connection:Keep-Alive

Content-Type:text/html; charset=UTF-8

通过这个清单,COS服务器告诉客户端:服务器是什么配置、你的协议我接受了、我给你的内容是什么格式等等信息。

当用RCurl这款客户端时,我们需要一一配置提交给服务器的内容,所以不妨先随心所欲、照葫芦画瓢的模仿一下上面的头信息:
myHttpheader<- c(<br />
"User-Agent"="Mozilla/5.0(Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.6)",<br />
"Accept"="text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",<br />
"Accept-Language"="en-us",<br />
"Connection"="keep-alive",<br />
"Accept-Charset"="GB2312,utf-8;q=0.7,*;q=0.7"<br />
)<br />


然后可以运用getURL函数实现cURL的网页浏览:
temp<- getURL("http://cos.name/",httpheader=myHttpheader)


返回值temp为一个字符串,其实就是我们用普通浏览器的“页面另存为”->“html文件”中所包含的内容。

如何看getURL的头信息呢?不妨再多设定几个RCurl的参数:


d =debugGatherer()<br />
temp<- getURL("http://cos.name/",httpheader=myHttpheader,<br />
debugfunction=d$update,verbose= TRUE)<br />


此时d中包含了你所要的信息,其中:
cat(d$value()[3])


中为RCurl提交服务器的头信息,而
cat(d$value()[2])


中为服务器端返回的头信息。看看跟正常浏览器的交互内容是不是一样呢?怎么看一般浏览器的头信息呢?如果你用Firefox的话,用扩展Live http headers就可以了。



三、RCurlHandles

在RCurl的目前版本中,有170多个(!!!!!!!!!)cURL系统参数可以设置,具体可以用
names(getCurlOptionsConstants())


查看一下,各个参数的详细说明则可以参照libcurl的官方说明文档

如此众多参数,如果每次都设定,是不是会非常的繁琐?幸好在RCurl中有一个非常强大的功能可以有效的解决这个问题:那就是cRULhandles(当然,cRUL handles的优势不止这一个)。cRULhandles类似于行走江湖的一个百宝箱:根据自己的喜好设好后,每次背箱出发就行了。同时cRULhandles还根据客户端、服务器端参数的设定在动态的变化,随时更新内容。如下便定义了一个最基本的cRULhandles:


cHandle<- getCurlHandle(httpheader = myHttpheader)<br />
在getURL中可以如下应用:<br />
d =debugGatherer()<br />
temp <- getURL("http://cos.name/", .opts = list(debugfunction=d$update,verbose = TRUE), curl=cHandle)
</pre> 此时,cHandle中的cRUL系统参数debugfunction、verbose均发生及时的更新。



四、用RCurl实现直接登录

上面提及的getURL函数仅仅实现了页面浏览的最简单功能。如果想用RCurl登录到某个网站(如http://cos.name/bbs/)怎么实现呢?还是继续看看你在正常登录过程中,客户端提交给服务器端的信息吧,然后照葫芦画一个。

http://cos.name/bbs/login.php?



POST/bbs/login.php? HTTP/1.1

Host:cos.name

User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.6)Gecko/20091201 Firefox/3.5.6

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language:en-us

Accept-Encoding:gzip,deflate

Accept-Charset:GB2312,utf-8;q=0.7,*;q=0.7

Keep-Alive:300

Connection:keep-alive

Referer:http://cos.name/bbs/login.php

Content-Type:application/x-www-form-urlencoded

Content-Length:110

forward=&jumpurl=http%3A%2F%2Fcos.name%2Fbbs%2F&step=2&lgt=0&pwuser=yourname&pwpwd=yourpw&hideid=0&cktime=31536000

这个头信息的核心就是客户端用POST方法给http://cos.name/bbs/login.php?提交了一个字符串(即头信息最后的两行,其中包含了你的用户名和密码),请求服务器端给予身份认证。至于为什么提交那么稀奇古怪的一串字符,就要问谢老大了:)。

如果要让RCurl提交相同的字符串,需要将上面的那段关键字符串转变成如下的格式:

c(name1=”info1”,name2=”info2”,…)。字符串的处理工作交给R就行了:
myPost<- function(x){<br />
 post <-scan(x,what="character",quiet=TRUE,sep="\n")<br />
 abcd=strsplit(post,"&")[[1]]<br />
 abc=gsub("(^.*)(=)(.*$)","\\3",abcd)<br />
 abcnames=gsub("(^.*)(=)(.*$)","\\1",abcd)<br />
 names(abc)=abcnames<br />
 return(abc)<br />
}<br />
postinfo<- myPost("clipboard")<br />


然后用RCurl中的postForm函数,将postinfo提交给服务器:
temp<- postForm("http://cos.name/bbs/login.php?",.params=postinfo,<br />
 .opts=list(cookiefile=""),curl=cHandle,style="post")<br />



cat(d$value()[2])


查看一下cos.name给你的客户端反馈了那些内容?作为登录认证的cookies是不是已经在里面了?到这一步,RCurl已经成功的登录到http://cos.name/bbs/了,需要的一切认证信息都已经记录到百宝箱cHandle中了。用
getCurlInfo(cHandle)[["cookielist"]]


看看你想要的cookie是不是在那里了?接着用cHandle登录一下R子论坛吧,验证一下你是否真正的成功了?
temp<- getURL("http://cos.name/bbs/thread.php?fid=15",<br />
curl=cHandle,.encoding="gbk")<br />


五、用RCurl实现间接登录

由于“这事儿不能说太细”,我们有时候不能用上面的方法来完成RCurl登录认证。那能不能让RCurl来使用其他浏览器客户端与服务器端已经建立好的认证呢?答案是可以尝试一下的:)所谓的认证信息一般就是服务器端在你的浏览器里面写下的cookies,把他们导出来交给RCurl,RCurl同样可以做好你需要的cURLhandle。

先用你的常规浏览器(此处假定为Firefox)正常登录到http://cos.name/bbs/,然后再用Firefox的扩展Firecookie看看当前页面的cookie信息:你需要的就是它们了。将这些cookie信息导出成RCurl能够识别的格式,然后提交给RCurl就万事大吉了。


d2 =debugGatherer()<br />
cHandle2<- getCurlHandle(httpheader=myHttpheader,followlocation=1,<br />
 debugfunction=d2$update,verbose=TRUE,<br />
 cookiefile="yourcookiefile.txt")<br />


接着去cos.name的R论坛看看:


temp<- getURL("http://cos.name/bbs/thread.php?fid=15",<br />
curl=cHandle2,.encoding="gbk")<br />


验证一下temp里面是不是已经有你的大名了呢?
grep("yourname",temp)


如果有的话那么恭喜你:RCurl已经成功接管你的登录权限了。



六、登录后RCurl能继续干什么

实现了登录认证的RCurl handles,这仅仅是第一步。能用它和R+RCurl继续做些什么呢?这时候,只要闭上眼睛、海阔天空的想一下平时怎么样用浏览器就有答案了:

1、能不能让RCurl帮我数一下某VIP网络俱乐部中王小麻子灌了多少水?

2、能不能帮顶一下王二麻子发表的美女yy贴?

3、为了给我的外甥女选秀投票,点鼠标点的手都抬不起来了,能不能让RCurl来帮我做呢?

4、我天天去某网站下载文档,绝对的体力活!

5、半夜起来偷菜,太困了,交给RCurl做就好了。

6、用RCurl玩twitter、写博客就好了。

7、我就想用RCurl看门户网站的体育新闻。

8、我就想在各个网站的论坛上发个“顶”字,顺便留下我的牛皮膏药小广告。

9、……

天有多高,RCurl有多强……



七、结束语

没有想到会唠叨这么多的废话。但这篇水文仅涉及到libcurl、RCurl中的一点皮毛而已,更多的内容请参考DuncanTemple Lang写的RCurl帮助文档libcurl官网。客观讲cURL属于浏览器中的一把剪刀,由于它强大的易编程属性,RCurl会带来一些意想不到的破坏性。但要记住:技术本身可能是无罪的,任何的破坏都可能是我们自己造成的。网络中的ID是现实中你的一个延伸,她同样有完整的人格和生命力,所以请尊重和爱护网络中的自己。

最后,希望这篇水文没有影响到你的好心情。

非常好的帖子,发回邮箱收藏了
鉴定完毕,新生代Geek一个……



不过有两个疑问:

1.“至于为什么提交那么稀奇古怪的一串字符”,我没看到啥稀奇古怪的字符啊,lz是指?

2.那个间接登录……怎么有点像Cookie伪装的说?不过没道理啊,按说现在的服务器端程序除了Cookie还会验证一下session的吧?



至于那个自动投票……问题更在于验证码和IP吧?这个古老的话题其实解决办法蛮多的,其实写个自动刷新和提交的JS脚本就OK了(如果不用换IP的话)。
他指的应该是forward=&jumpurl=http%3A%2F%2Fcos.name%2Fbbs%2F&step=2&lgt=0&pwuser=yourname&pwpwd=yourpw&hideid=0&cktime=31536000;这些东西顾名可思义,jumpurl就是登录之后要跳转的页面,%3A这种东西时url_encode之后的字符(冒号),pw是phpwind,user和pwd就不用说了吧,hideid应该是指是否隐身登录,cktime用的是Unix时间戳。



这位兄台很能写,风格也很搞,可以考虑加盟主站啊,哈哈。
回4楼:

1、那个字符串就是5楼的谢博说的那串。

2、名字就随便起了,我完全是随性起得。据我所知,session验证的安全性也有限。

自动投票的问题跟你说的一样。ip和验证码识别是关键。

另外新生代geek是什么呢?搜了一下,有褒义有贬义,我应该二者都不是:)







回5楼:

风格搞,完全是因为才疏学浅啊,开始写的时候才知道这个东西不象我开始想的那样那么的容易写,所以开了个头,就匆匆的收尾。因为curl除了要求精通英语、日语、法语外,还要精通html、xml、js、c等等各国专业语言,可我都不懂。写这个部分原因是看到你最近的文章所用的数据源。部分的原因是看到论坛没有说过这件事请,就先抛砖头了。



加盟主站,这个可是2010年的第一份offer啊,哈哈。不知道有什么要求、义务和权力呢?如果要求不多,我就厚脸皮的应了。呵呵。
发一份自我介绍到contact@cos.name,注明一个你想要的用户名,随后会有人把注册信息发给你,谢谢



权利嘛,基本上木有啥权利,等COS发达了(麦太口头禅),你想吃肉吃肉,想喝酒喝酒。义务嘛,基本上也木有,在你有空的时候琢磨几篇小文章发发就可以。



一堆繁文缛节在此:http://cos.name/2008/11/how-to-work-with-cos/
[quote]引用第6楼medo于2010-01-03 14:08发表的  :



另外新生代geek是什么呢?搜了一下,有褒义有贬义,我应该二者都不是:)



[/quote]



这个,没啥特别的意思,就是觉得有研究精神的都算作geek,新生代是觉得你很年轻……【当然这个未经验证,不过潜台词是“前途无量”】



另,“陈博”这个称呼怪怪的,和我一同学名字一致。我也不是博士,算个博主也不够称职,你还是直接称cloudly吧……(说起来我想改下论坛ID了……)
[quote]引用第8楼临泽而渔于2010-01-06 16:12发表的  :



另,“陈博”这个称呼怪怪的,和我一同学名字一致。我也不是博士,算个博主也不够称职,你还是直接称cloudly吧……(说起来我想改下论坛ID了……)

[/quote]



看来是我错了:)已经受到2次点名批评了,尽管我是出于尊重,看来还是直称ID来的自然些。
其实核心起来一句话curl很厉害,R中调用libcurl来干活,熟悉curl就知道这个pkg的功能了。

要想这个用的顺溜,关键在正则表达式方面的东东——而这个利器的娴熟使用,颇需要花费功夫啊。



btw,以前使用Python来玩过,因为有个叫pycurl的东东。
哈哈,不是批评,有些人喜欢被人称呼头衔,有人不喜欢,更何况我和cloudly都不算博士。我老板就属于后者,她对我很满意的一点就是,我从来都直呼其名 [s:11]



本文交代了杀人利器,要是能去杀个人放把火就完美了,可以直接发表到主站上了,比如找几个统计相关主题,用RCurl去收集数据,然后R分析
25 天 后
前辈好!我试着用RCurl整理在线数据,现在遇到的问题是,有些中文网站比如百度,中文字符都是乱码,其他数据正常。我检查了httpheader中的Accept-Charset,应该没有问题,请问会是什么原因呢?
呵呵,找到原因了,同样是cos的网页,一个主站一个BBS,如果“依葫芦画瓢”用相同代码的话,主站正常读取,BBS的中文为乱码。

myHttpheader<- c("User-Agent"="Mozilla/5.0(Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.6)","Accept"="text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Accept-Language"="en-us/zh-CN","Connection"="keep-alive")

temp1<- getURL("http://cos.name/",httpheader=myHttpheader)

temp2<- getURL("http://cos.name/bbs/index.php",httpheader=myHttpheader)

开始以为是httpheader中Accept-Charset的问题,后来发现这是与表单有关的,在这段代码中甚至多余。

后来对主站网页进行“文件-另存为-文本文件”操作时,发现默认编码是unicode(utf-8),但BBS的编码是GB2312,终于找到原因了,所以把BBS的代码改为:

temp2<- getURL("http://cos.name/bbs/index.php",httpheader=myHttpheader,.encoding="GB2312")



汗……虽然只是很小的一步,对晚辈而言真不容易呀
呃,不好意思啊,主站是utf8编码,论坛是gb2312……这个状况即将改变了,将来全站都会是统一utf8



你很厉害了
13 天 后
2 个月 后

回复 第17楼 的 talentyxc:没有有写出来啊。

此外,发现读取速度很慢啊,我想把整个论坛的帖子都读取下来:(

2 个月 后

回复 第15楼 的 微微:

require(RCurl)<br />
site <- getURL('http://liansai.500wan.com/seasonindex.php?seasonid=1916&stid=4079&gourl=season_game_list',encoding='GB2312')<br />
txt <- readLines(tc <- textConnection(site)); close(tc)<br />
txt[1:30] #随意列出1:30行,还是一样是乱码<br />
Encoding(txt)<br />
[1]"latin1"


从以上的网站导入的中文字出现乱码。无法成功encode...</p>

require(RCurl)<br />
site <- getURL('http://news.google.com.hk', encoding='GB2312')<br />
txt <- readLines(tc <- textConnection(site)); close(tc)<br />
txt<br />
Encoding(txt)<br />
[1]"UTF-8"


从以上的网站导入的中文字一切正常。</p>

好奇怪,为什么会这样呢?我试过Windows(64bits) 和 Windows7(32bits)也是一样无法解决...[s:14]

回复 第18楼 的 aristotle:

用python代码:

<br />
#!/usr/bin/env python<br />
# -*- coding: GBK -*-</p>
<p>import urllib</p>
<p>from sgmllib import SGMLParser</p>
<p>class URLLister(SGMLParser):<br />
    def reset(self):<br />
        SGMLParser.reset(self)<br />
        self.urls = []</p>
<p>    def start_a(self, attrs):<br />
        href = [v for k, v in attrs if k == 'href']<br />
        if href:<br />
            self.urls.extend(href)</p>
<p>url = r'http://cos.name/cn/'<br />
sock = urllib.urlopen(url)<br />
htmlSource = sock.read()<br />
sock.close()<br />
#print htmlSource<br />
f = file('COS.html', 'w')<br />
f.write(htmlSource)<br />
f.close()<br />
</p>

语法看起来很像R是吧?

当然,只爬了一重,多重的没有做。提示下,Python的空格不要随便改...感觉上直接把Python的urllib 给port到R上面会很舒服,至少语法不会那么奇怪。