rvest & urllib2 简介

Fibears

2016.03.29


In [2]:
# 载入rpy2.ipython
# rpy2提供了Python和R之间的交互环境
%load_ext rpy2.ipython

rvest

rvest是大神Hadley的作品,对于结构比较良好的网页,利用rvest, CSS/XPath选择器和管道符号来处理效率最高。

rvest包里面主要有以下几个函数:

  • read_html(x, ..., encoding = "", as_html = FALSE): 既可以从网络中获取html文档,也可以从本地中载入html文档;
  • html_nodes(x, css, xpath): 利用css和xpath选择器从html文档中提取出节点信息;
  • html_text(x): 提取所有满足条件的文本信息;
  • html_attrs(x): 提取所有满足条件的属性信息;
  • html_table(x, header = NA, trim = TRUE, fill = FALSE, dec = "."): 提取表格信息;
  • html_session(), jump_to(), follow_link(), back(), forward(): 这些都是用于模拟浏览网站

In [3]:
%%R
library(rvest)
# vignette("selectorgadget")
lego_movie <- read_html("http://www.imdb.com/title/tt1490017/")


/anaconda/lib/python2.7/site-packages/rpy2/robjects/functions.py:106: UserWarning: 载入需要的程辑包:xml2

  res = super(Function, self).__call__(*new_args, **new_kwargs)

In [4]:
%%R
lego_movie


{xml_document}
<html>
[1] <head>\n        <meta charset="utf-8"/>\n        <meta http-equiv="X-UA-C ...
[2] <body id="styleguide-v2" class="fixed">\n<script><![CDATA[\n    if (typeo ...

In [8]:
%%R
rating <- lego_movie %>% 
    html_nodes("strong span")
rating


{xml_nodeset (1)}
[1] <span itemprop="ratingValue">7.8</span>

In [9]:
%%R
cast <- lego_movie %>%
    html_nodes("#titleCast .itemprop span") %>%
    html_text()
cast


 [1] "Will Arnett"     "Elizabeth Banks" "Craig Berry"     "Alison Brie"    
 [5] "David Burrows"   "Anthony Daniels" "Charlie Day"     "Amanda Farinos" 
 [9] "Keith Ferguson"  "Will Ferrell"    "Will Forte"      "Dave Franco"    
[13] "Morgan Freeman"  "Todd Hansen"     "Jonah Hill"     

In [10]:
%%R
poster <- lego_movie %>%
    html_nodes(xpath="//div[@class='poster']/a/img") %>%
    html_attr("src")
poster


[1] "http://ia.media-imdb.com/images/M/MV5BMTg4MDk1ODExN15BMl5BanBnXkFtZTgwNzIyNjg3MDE@._V1_UX182_CR0,0,182,268_AL_.jpg"

urllib*模块

首先,urllib*中包含了urllib, urllib2和urllib3等几个模块。其中urllib3是第三方扩展模块,所以在这里我们只讨论前两个Python自带模块。那么这两个模块之间到底有何区别呢?

官方文档对urllib和urllib2分别是这样描述的:“通过url打开任意资源”和“打开url的拓展库”。urllib2主要用于处理一些更复杂的操作,比如操作相关的一些认证、重定向和cookie等等。

urllib模块

urllib模块中主要有以下几个方法:

  • urllib.urlopen(url, data, proxies): 向url发出一个请求,并获取服务器返回的文件对象;

In [11]:
import urllib
f = urllib.urlopen('http://www.baidu.com')
firstLine = f.readline()
firstLine


Out[11]:
'<!doctype html>\n'
  • urllib.urlretrieve(url, filename, reporthook, data): 将url对应的html文件下载到本地电脑中;

In [12]:
file = urllib.urlretrieve('http://www.baidu.com/', filename='baidu.html')
file


Out[12]:
('baidu.html', <httplib.HTTPMessage instance at 0x115da2830>)
  • quote(): 将url中的特殊字符或汉字encode成指定编码;
  • unquote(): 将url中的url编码解码;
  • urllib.urlencode(query): 将URL中的数据对以连接符&连接起来,作为post方法和get方法的请求参数

In [13]:
urllib.quote('经济学院')


Out[13]:
'%E7%BB%8F%E6%B5%8E%E5%AD%A6%E9%99%A2'

In [14]:
print urllib.unquote('%E7%BB%8F%E6%B5%8E%E5%AD%A6%E9%99%A2')


经济学院

In [16]:
Data = urllib.urlencode({'UserName':'fibears','PassWd':123456})
Data


Out[16]:
'UserName=fibears&PassWd=123456'

In [11]:
# GET 方法
# GET方式是直接以链接形式访问,链接中包含了所有的参数。
response = urllib.urlopen("http://event.wisesoe.com/Logon.aspx" + '?' + Data)
# response.read()

In [17]:
"http://event.wisesoe.com/Logon.aspx" + '?' + Data


Out[17]:
'http://event.wisesoe.com/Logon.aspx?UserName=fibears&PassWd=123456'

In [13]:
# POST 方法
# POST将参数以变量的形式传递给处理器,所以不会在网址上显示所有的参数。
response = urllib.urlopen("http://event.wisesoe.com/Logon.aspx",Data)
# response.read()

urllib2模块

urllib2模块中主要有以下几个方法:

  • urllib2.urlopen(url, data, timeout): 向url发出一个请求,并获取服务器返回的文件对象,该方法还可以接受一个Request类的实例;

In [5]:
import urllib2
response1 = urllib2.urlopen("http://www.baidu.com")
# print response1.read()
  • urllib2.Request(url, data, headers): 构建一个请求(request)对象;

In [6]:
request = urllib2.Request('http://www.baidu.com')
response2 = urllib2.urlopen(request)
# print response2.read()

In [16]:
# GET 方法
Data = urllib.urlencode({'UserName':'fibears','PassWd':123456})
GetUrl = "http://event.wisesoe.com/Logon.aspx" + '?' + Data
print GetUrl
request = urllib2.Request(GetUrl)
response = urllib2.urlopen(request)


http://event.wisesoe.com/Logon.aspx?UserName=fibears&PassWd=123456

In [17]:
# POST 方法
Data = urllib.urlencode({'UserName':'fibears','PassWd':123456})
Url = "http://event.wisesoe.com/Logon.aspx"
request = urllib2.Request(Url, Data)
response = urllib2.urlopen(request)

有些网站不会同意程序直接用上面的方式进行访问,站点根本不会响应我们所发出的简单请求,所以为了完全模拟浏览器的工作,我们需要设置一些Headers的属性。以下是几个常用的 Headers 属性:

  • "User-Agent": 表明了你的浏览器版本和系统信息;
  • "Host": 代表基本的主机名;
  • "Cookie": 浏览器所存储的Cookie信息;
  • "Referer": 主要用于让服务器判断来源页面, 即用户是从哪个页面跳转过来的;

In [18]:
Data = urllib.urlencode({'UserName':'fibears','PassWd':123456})
user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:45.0) Gecko/20100101 Firefox/45.0'
headers = {'User-Agent' : user_agent,
          'Referer': 'http://event.wisesoe.com/Authenticate.aspx?returnUrl=/LectureOrder.aspx'}
Url = "http://event.wisesoe.com/LectureOrder.aspx"
request = urllib2.Request(Url, Data, headers)
response = urllib2.urlopen(request)

有的网站会检测某一段时间内某个 IP 访问网页的次数,如果访问过于频数,它会禁止该 IP 对网页的访问。这种情况下,我们通常有两种处理办法,一是设置延迟机制,降低爬虫程序发出请求的频率;二是设置代理服务器,每隔一段时间更换一个代理,这样就不会被躲过网站的检测机制。我们可以利用urllib2.ProxyHandler方法来设置代理:


In [19]:
# 设置代理
import urllib2

proxy_handler = urllib2.ProxyHandler({"http" : 'http://some-proxy.com:8080'})
opener = urllib2.build_opener(proxy_handler)
# urllib2.install_opener(opener)

Cookie的使用方法

Cookie指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据。

有些网站需要登录后才能访问某个页面,在登录之前,我们无法获取网页的内容。这种情况下我们可以利用urllib2库保存登录网页的Cookie,然后再利用该Cookie来抓取其他页面。

步骤:

  • 构建一个带有Cookie的处理器
  • 模拟登陆网页
  • 获取Cookie
  • 将Cookie保存到本地
  • 读取Cookie并构建用于访问网页的opener
  • 发出HTTP请求,并得到返回的响应文件

In [8]:
import urllib
import urllib2
import cookielib

# 声明cookie文件的存储路径
CookieFile = "cookie.txt"

# 构建一个MozillaCookieJar对象来保存cookie文件
cookie = cookielib.MozillaCookieJar(CookieFile)

# urlopen()方法就是一个特殊的opener
# 构建一个带有Cookie的处理器opener
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))

# 利用处理器opener发出HTTP请求
Data = urllib.urlencode({'UserName':'fibears','PassWd':123456})
user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:45.0) Gecko/20100101 Firefox/45.0'
headers = {'User-Agent' : user_agent,
          'Referer': 'http://event.wisesoe.com/Authenticate.aspx?returnUrl=/LectureOrder.aspx'}
LectureUrl = "http://event.wisesoe.com/LectureOrder.aspx"
request = urllib2.Request(LectureUrl, Data, headers)
response = opener.open(request)
print cookie

# 将Cookie保存到本地
# ignore_discard: 即使浏览网页过程中cookie被丢弃也将其保存下来
# ignore_expires: 对于文件中已经存在的cookie,将其覆盖并写入新的信息
cookie.save(ignore_discard=True, ignore_expires=True)

# 读取Cookie并构建用于访问网页的opener
cookie = cookielib.MozillaCookieJar()
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))

# 发出HTTP请求,并得到返回的响应文件
request = urllib2.Request(LectureUrl, Data, headers)
response = opener.open(request)
# print response.read()


<MozillaCookieJar[<Cookie ASP.NET_SessionId=z253wmqahthos4ia3ivwqyv4 for event.wisesoe.com/>]>

豆瓣电影爬虫程序

接下来我以结构比较简单的豆瓣网站为例,分别介绍如何利用rvest和urllib2从网上爬取数据。


In [21]:
%%R
library(RCurl)
library(rvest)
library(stringr)
library(plyr)
library(dplyr)


/anaconda/lib/python2.7/site-packages/rpy2/robjects/functions.py:106: UserWarning: 载入需要的程辑包:bitops

  res = super(Function, self).__call__(*new_args, **new_kwargs)
/anaconda/lib/python2.7/site-packages/rpy2/robjects/functions.py:106: UserWarning: 
载入程辑包:‘dplyr’


  res = super(Function, self).__call__(*new_args, **new_kwargs)
/anaconda/lib/python2.7/site-packages/rpy2/robjects/functions.py:106: UserWarning: The following objects are masked from ‘package:plyr’:

    arrange, count, desc, failwith, id, mutate, rename, summarise,
    summarize


  res = super(Function, self).__call__(*new_args, **new_kwargs)
/anaconda/lib/python2.7/site-packages/rpy2/robjects/functions.py:106: UserWarning: The following objects are masked from ‘package:stats’:

    filter, lag


  res = super(Function, self).__call__(*new_args, **new_kwargs)
/anaconda/lib/python2.7/site-packages/rpy2/robjects/functions.py:106: UserWarning: The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union


  res = super(Function, self).__call__(*new_args, **new_kwargs)

In [23]:
%%R
# 爬取豆瓣电影TOP250的数据
# 获取豆瓣电影首页URL
DoubanUrl <- 'http://movie.douban.com/top250'

# 从首页中获取所有页面的URL
PageUrlList <- read_html(DoubanUrl) %>% 
    html_nodes(xpath = "//div[@class='paginator']/a") %>% 
    html_attr("href") %>% 
    str_c(DoubanUrl, ., sep="") %>% c(DoubanUrl,.)
PageUrlList


 [1] "http://movie.douban.com/top250"                  
 [2] "http://movie.douban.com/top250?start=25&filter=" 
 [3] "http://movie.douban.com/top250?start=50&filter=" 
 [4] "http://movie.douban.com/top250?start=75&filter=" 
 [5] "http://movie.douban.com/top250?start=100&filter="
 [6] "http://movie.douban.com/top250?start=125&filter="
 [7] "http://movie.douban.com/top250?start=150&filter="
 [8] "http://movie.douban.com/top250?start=175&filter="
 [9] "http://movie.douban.com/top250?start=200&filter="
[10] "http://movie.douban.com/top250?start=225&filter="

In [25]:
%%R
# 从每个PageUrl中提取出每部电影的链接
MovieUrl <-  NULL
for (url in PageUrlList) {
    item = read_html(url) %>% 
        html_nodes(xpath="//div[@class='hd']/a") %>% 
        html_attrs("href")
    MovieUrl = c(MovieUrl, item)
}
head(MovieUrl,5)


[1] "https://movie.douban.com/subject/1292052"
[2] "https://movie.douban.com/subject/1295644"
[3] "https://movie.douban.com/subject/1292720"
[4] "https://movie.douban.com/subject/1291546"
[5] "https://movie.douban.com/subject/1292063"

In [24]:
%%R
# 从每个MovieUrl中提取出最终的数据
## 定义函数Getdata,用于获取数据并输出dataframe格式
GetImdbScore <- function(url){
    ImdbScore = read_html(url) %>% 
        html_nodes(xpath = "//span[@itemprop='ratingValue']/text()") %>% 
        html_text()
    return(ImdbScore)
}
Getdata <- function(url){
    Movie = url
    if(url.exists(url)){
        MovieHTML = read_html(url, encoding = 'UTF-8')
        Rank = html_nodes(MovieHTML, xpath = "//span[@class='top250-no']/text()") %>% html_text()
        MovieName = html_nodes(MovieHTML, xpath = "//span[@property='v:itemreviewed']/text()") %>% html_text()
        Director = html_nodes(MovieHTML, xpath = "//a[@rel='v:directedBy']/text()") %>% 
            html_text() %>% paste(collapse = ";")
        Type = html_nodes(MovieHTML, xpath = "//span[@property='v:genre']/text()") %>% 
            html_text() %>% paste(collapse = ";")
        Score = html_nodes(MovieHTML, xpath = "//strong[@property='v:average']/text()") %>% html_text()
        ImdbUrl = html_nodes(MovieHTML, xpath = "//a[contains(@href,'imdb')]/@href") %>% html_text()
        ImdbScore = GetImdbScore(ImdbUrl) 
        Description = html_nodes(MovieHTML, xpath = "//span[@property='v:summary']/text()") %>% 
            html_text() %>% str_replace("\n[\\s]+", "") %>% paste(collapse = ";")
        data.frame(Rank, Movie, MovieName, Director, Type, Score, ImdbScore, Description)
    }
}

In [25]:
%%R
## 抓取数据
Douban250 <- data.frame()
for (i in 1:2) {
    Douban250 = rbind(Douban250, Getdata(MovieUrl[i]))
    print(paste("Movie",i,sep = "-"))
    Sys.sleep(round(runif(1,1,3)))
}
Douban250
# for (i in 1:length(MovieUrl)) {
#     Douban250 = rbind(Douban250, Getdata(MovieUrl[i]))
#     print(paste("Movie",i,sep = "-"))
#     Sys.sleep(round(runif(1,1,3)))
# }


Error in Getdata(MovieUrl[i]) : 找不到对象'MovieUrl'
/anaconda/lib/python2.7/site-packages/rpy2/robjects/functions.py:106: UserWarning: Error in Getdata(MovieUrl[i]) : 找不到对象'MovieUrl'

  res = super(Function, self).__call__(*new_args, **new_kwargs)

In [9]:
%%R
# 豆瓣API
url <- "https://api.douban.com/v2/movie/1292052"
library(rvest)
result <- read_html(url)
result <- html_nodes(result, "p") %>% html_text()

In [39]:
%%R
class(result)


[1] "character"

In [28]:
%%R
# 这是json(javascript online notation)格式的文件,可以利用rjson中的函数fromJSON将其转化为结构化的数据。
Movie = rjson::fromJSON(result)

In [29]:
%%R
Movie


$rating
$rating$max
[1] 10

$rating$average
[1] "9.6"

$rating$numRaters
[1] 684289

$rating$min
[1] 0


$author
$author[[1]]
$author[[1]]$name
[1] "弗兰克·德拉邦特 Frank Darabont"



$alt_title
[1] "肖申克的救赎 / 月黑高飞(港)"

$image
[1] "https://img1.doubanio.com/view/movie_poster_cover/ipst/public/p480747492.jpg"

$title
[1] "The Shawshank Redemption"

$summary
[1] "20世纪40年代末,小有成就的青年银行家安迪(蒂姆·罗宾斯 Tim Robbins 饰)因涉嫌杀害妻子及她的情人而锒铛入狱。在这座名为肖申克的监狱内,希望似乎虚无缥缈,终身监禁的惩罚无疑注定了安迪接下来灰暗绝望的人生。未过多久,安迪尝试接近囚犯中颇有声望的瑞德(摩根·弗里曼 Morgan Freeman 饰),请求对方帮自己搞来小锤子。以此为契机,二人逐渐熟稔,安迪也仿佛在鱼龙混杂、罪恶横生、黑白混淆的牢狱中找到属于自己的求生之道。他利用自身的专业知识,帮助监狱管理层逃税、洗黑钱,同时凭借与瑞德的交往在犯人中间也渐渐受到礼遇。表面看来,他已如瑞德那样对那堵高墙从憎恨转变为处之泰然,但是对自由的渴望仍促使他朝着心中的希望和目标前进。而关于其罪行的真相,似乎更使这一切朝前推进了一步……\n本片根据著名作家斯蒂芬·金(Stephen Edwin King)的原著改编。"

$attrs
$attrs$pubdate
[1] "1994-09-10(多伦多电影节)" "1994-10-14(美国)"        

$attrs$language
[1] "英语"

$attrs$title
[1] "The Shawshank Redemption"

$attrs$country
[1] "美国"

$attrs$writer
[1] "弗兰克·德拉邦特 Frank Darabont" "斯蒂芬·金 Stephen King"        

$attrs$director
[1] "弗兰克·德拉邦特 Frank Darabont"

$attrs$cast
 [1] "蒂姆·罗宾斯 Tim Robbins"           "摩根·弗里曼 Morgan Freeman"       
 [3] "鲍勃·冈顿 Bob Gunton"              "威廉姆·赛德勒 William Sadler"     
 [5] "克兰西·布朗 Clancy Brown"          "吉尔·贝罗斯 Gil Bellows"          
 [7] "马克·罗斯顿 Mark Rolston"          "詹姆斯·惠特摩 James Whitmore"     
 [9] "杰弗里·德曼 Jeffrey DeMunn"        "拉里·布兰登伯格 Larry Brandenburg"
[11] "尼尔·吉恩托利 Neil Giuntoli"       "布赖恩·利比 Brian Libby"          
[13] "大卫·普罗瓦尔 David Proval"        "约瑟夫·劳格诺 Joseph Ragno"       
[15] "祖德·塞克利拉 Jude Ciccolella"    

$attrs$movie_duration
[1] "142 分钟"

$attrs$year
[1] "1994"

$attrs$movie_type
[1] "犯罪" "剧情"


$id
[1] "http://api.douban.com/movie/1292052"

$mobile_link
[1] "http://m.douban.com/movie/subject/1292052/"

$alt
[1] "http://movie.douban.com/movie/1292052"

$tags
$tags[[1]]
$tags[[1]]$count
[1] 144089

$tags[[1]]$name
[1] "经典"


$tags[[2]]
$tags[[2]]$count
[1] 118188

$tags[[2]]$name
[1] "励志"


$tags[[3]]
$tags[[3]]$count
[1] 99575

$tags[[3]]$name
[1] "信念"


$tags[[4]]
$tags[[4]]$count
[1] 85091

$tags[[4]]$name
[1] "自由"


$tags[[5]]
$tags[[5]]$count
[1] 71856

$tags[[5]]$name
[1] "美国"


$tags[[6]]
$tags[[6]]$count
[1] 58312

$tags[[6]]$name
[1] "人性"


$tags[[7]]
$tags[[7]]$count
[1] 37275

$tags[[7]]$name
[1] "剧情"


$tags[[8]]
$tags[[8]]$count
[1] 37037

$tags[[8]]$name
[1] "人生"




In [ ]: