之前没有怎么用过python,也没写过爬虫,最近几天抽空学习了一下,写了个人人网的爬虫练了练手。
用了BeautifulSoup4包来解析HTML标签,Beautiful Soup 是用 Python 写的一个 HTML/XML 的解析器,它可以很好的处理不规范标记并生成剖析树。通常用来分析爬虫抓取的web文档。对于不规则的 Html文档,也有很多的补全功能,节省了开发者的时间和精力。使用起来类似于DOM,还是非常方便的。
地址:http://www.crummy.com/software/BeautifulSoup/
非常良心的中文文档,看过就会:http://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
在这里先推荐两个工具,Windows下的fiddler,Linux下基于Firefox的Firebug,前者我没有具体用过,但基本上所有人都说是windows开发下利器,大家可以尝试以下。至于Firebug,也就是类似于chrome的审查元素,只不过是基于Firefox浏览器的,功能还是相当完善的,Firebug如图。
具体实现思路:首先我们知道人人网数据不公开,所以想要获取数据,必须先登陆到人人网,那么用户是怎么登陆的呢,其实写过网页的都知道所谓的登陆只是向服务器的某个特定的链接发出了一个Post请求,这个请求里包含了用户登陆的数据,比如用户名,密码,以及其他一些可能的参数,我们只要能够知道Post的对象,便可以模拟登陆了。
我们先进入到人人网,然后打开浏览器的审查元素(或者Firebug,或者其他),打开网络选项,输入用户名密码之后,点击登陆,查看所有抓取的包中类型为post的包,可以看到很多详细的信息。
同时我们还需要生成一个保存cookie的东西,Python提供一个非常方便的自动保存cookie的组件Cookielib。
还有一个问题就是,之所以要选择3g.renren.com而不是www.renren.com,是因为考虑到网页版人人在”所有状态“的页面内容是通过Ajax生成的,而且获得的数据结构非常乱不管是用BeautifulSoup,还是用正则匹配都比较麻烦,所以我们选择用比较简单的手机版页面来进行抓取。
关于Ajax页面的抓取方法,可以通过PhantomJS+selenium来调用虚拟浏览器获取加载好之后的网页,也可以人工抓包,抓取有用信息,然后通过GET操作获得返回的页面或者JSON文件。这里就不详述了。
登陆代码如下:
try:
self.cookie = cookielib.CookieJar() #设置登陆cookie
self.cookieProc = urllib2.HTTPCookieProcessor(self.cookie)
except:
raise
else:
opener = urllib2.build_opener(self.cookieProc)
opener.addheaders = [('User-Agent','Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11')]
#浏览器伪装
urllib2.install_opener(opener)
url='http://3g.renren.com/login.do' #登陆人人网3g首页
postdata = {
'email':self.email,
'password':self.password,
}
req = urllib2.Request(url,urllib.urlencode(postdata))
index = urllib2.urlopen(req).read()
indexSoup = BeautifulSoup(index) #首页BeautifulSoup对象,方便进行之后的标签提取等操作
其中的id和sid,是发送get请求需要填写的东西,这两个值在返回的首页html文件里面有,我们可以通过正则匹配把他们找出来。
对于链接的获取,比如进入到个人主页界面之后想点击状态链接,就可以通过如下代码实现:
url = profileSoup.select(‘.sec’)[5].find_all(‘a’)[3]['href']
其中profileSoup为个人主页页面生成的BeautifulSoup对象,.select(‘.sec’)是指选择所有class为sec的标签,以list方式呈现,find_all(‘a’)表示选择出所有的a标记,['href']表示获取href下的链接。
代码中有很多是通过这样”精确定位”的方式获取的链接,大家可以针对页面html文件来仔细研究。
访问到状态页面之后,我们便要开始获取状态信息,分析页面返回的html文件,我们可以发现,每一页上面有10个状态,并且状态都是以这样的div呈现的:
X月X日 xx:xx
这一条时间信息,因此这个东西可以作为特征值,来帮助我们找到每页的10个描述状态的div。进一步我们可以观察出以下规律:
第一个a标签之后的文字为我们的状态内容,如果文章为转发,则为我们转发之后回复的话。
而对于转发的文章,
下的子节点之后的内容为我们转发的原文。
非转发的文章,没有
标签。
页面爬取完成之后,需要获取下一页链接标签,而该标签的父节点的css样式为”class=l”,于是可以帮助我呢精确定位。
totalPageHtml = statusSoup.select(".gray")[0].contents
totalPage = re.findall(r"(?<=/)\d+(?=[^\d])",str(totalPageHtml)) #正则匹配,找到总共的页数
totalPage = int(totalPage[0])
print "总共有:"+str(totalPage)+"页"
nowPage = 1
while (nowPage<=totalPage) : #循环,直到爬取完所有页面的信息
print "当前正在获取第"+str(nowPage)+"页状态信息"
statusList = statusSoup.select(".list")[0].children
for child in statusList:
if (child.select(".time")):# Step1: 找时间戳,确定为状态信息,而不是子节点中不是状态信息的div
statusDate.append(child.select(".time")[0].string)
if (child.select(".forward")): #step2:找class名为forward的内容,这部分为转的状态
tempStr = str(child.a.next_element)
m = re.findall(r"^.*?(?=转自)",tempStr) #这里是把"转自"后面的内容删除掉,只保留自己回复的内容
if m:
statusContent.append(m[0])
else :
statusContent.append("无")
originStatusContent.append(child.select(".forward")[0].a.next_element.next_element)
else: #step3:这些是原创内容,直接保存
statusContent.append(child.a.next_element)
originStatusContent.append("无")
nowPage = nowPage+1
if (nowPage>totalPage): break
nextPageUrl =str(statusSoup.select(".l")[0].a['href']) #查找下一页URL并跳转
req = urllib2.Request(nextPageUrl)
statusFile = urllib2.urlopen(req).read()
statusSoup = BeautifulSoup(statusFile)
至此,我们就完成了所有的爬取工作,下面附上完整代码,实现功能是控制台输入用户名和密码,爬取完成之后保存到UserData/用户id 文件中。
#! /usr/lib/python
#-*- coding: utf-8 -*-
import time
import sys
import urllib
import urllib2
import cookielib
import os
import re
from bs4 import BeautifulSoup
#import js2html
class renrenSpider:
def __init__(self,email,password):
self.email = email
self.password = password
self.domain = 'renren.com'
self.id = ''
self.sid = ''
try:
self.cookie = cookielib.CookieJar()
self.cookieProc = urllib2.HTTPCookieProcessor(self.cookie)
except:
raise
else:
opener = urllib2.build_opener(self.cookieProc)
opener.addheaders = [('User-Agent','Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11')]
urllib2.install_opener(opener)
def login(self):
url='http://3g.renren.com/login.do' #登陆人人网3g首页
postdata = {
'email':self.email,
'password':self.password,
}
req = urllib2.Request(url,urllib.urlencode(postdata))
index = urllib2.urlopen(req).read()
indexSoup = BeautifulSoup(index) #首页soup
indexFile = open('index.html','w')
indexFile.write(indexSoup.prettify())
indexFile.close()
tmp = indexSoup.select('.cur')[0]
idHref = tmp.parent.contents[2]['href']
m = re.findall(r"\d{6,}",str(idHref)) #正则匹配获取用户的id
self.id = m[0]
print "用户ID为:" + self.id
m = re.findall(r"(?<=sid=).*?(?=&)",str(idHref)) #正则匹配获取用户的sid
self.sid = m[0]
print "用户的sid为:" + self.sid
def getStatus(self):
#获取个人状态页面
statusDate = [] #保存状态时间
statusContent = [] #保存状态内容
originStatusContent = [] #保存转发原文内容
url = 'http://3g.renren.com/profile.do' #登陆到个人主页
profileGetData = {
'id':str(self.id),
'sid':self.sid
}
req = urllib2.Request(url,urllib.urlencode(profileGetData))
profile = urllib2.urlopen(req).read()
profileSoup = BeautifulSoup(profile)
# print profileSoup.prettify()
url = profileSoup.select('.sec')[5].find_all('a')[3]['href'] #获得链接
req = urllib2.Request(url)
statusFile = urllib2.urlopen(req).read()
statusSoup = BeautifulSoup(statusFile)
statusFile = open("status.html",'w')
statusFile.write( statusSoup.prettify())
statusFile.close()
totalPageHtml = statusSoup.select(".gray")[0].contents
totalPage = re.findall(r"(?<=/)\d+(?=[^\d])",str(totalPageHtml))
totalPage = int(totalPage[0])
print "总共有:"+str(totalPage)+"页"
nowPage = 1
# totalPage = 15
while (nowPage<=totalPage) :
print "当前正在获取第"+str(nowPage)+"页状态信息"
statusList = statusSoup.select(".list")[0].children
for child in statusList:
#statusNum = statusNum+1
if (child.select(".time")):# Step1: 找时间戳,确定为状态信息
statusDate.append(child.select(".time")[0].string)
if (child.select(".forward")): #step2:找class名为forward的内容,这部分为转的状态
tempStr = str(child.a.next_element)
m = re.findall(r"^.*?(?=转自)",tempStr)
if m:
statusContent.append(m[0])
else :
statusContent.append("无")
originStatusContent.append(child.select(".forward")[0].a.next_element.next_element)
else: #step3:这些是原创内容,直接保存
statusContent.append(child.a.next_element)
originStatusContent.append("无")
nowPage = nowPage+1
if (nowPage>totalPage): break
nextPageUrl =str(statusSoup.select(".l")[0].a['href']) #查找下一页URL并跳转
req = urllib2.Request(nextPageUrl)
statusFile = urllib2.urlopen(req).read()
statusSoup = BeautifulSoup(statusFile)
# for state in statusList:
# print state.name
finalFile = open("UserData/"+self.id,"w")
for i in range (0,len(statusDate)):
finalFile.write("第"+str(i+1)+"条:"+"\n")
finalFile.write("时间:"+str(statusDate[i])+"\n")
finalFile.write("状态:"+str(statusContent[i])+"\n")
finalFile.write("转发原文:"+str(originStatusContent[i])+"\n")
finalFile.write("\n")
if __name__ == '__main__':
email = raw_input("输入人人网账号")
password = raw_input("输入人人网密码")
reload(sys)
sys.setdefaultencoding('utf-8')
renrenLogin = renrenSpider(email,password)
renrenLogin.login()
renrenLogin.getStatus()
搬运自个人博客http://www.xgezhang.com/python_renren_spider_1.html,欢迎转载,请注明出处