您的位置:首页 > 理论基础 > 计算机网络

Python中网络页面抓取和页面分析

2012-07-22 14:53 429 查看
转载本文请以链接形式注明出处。

1.前言

Python的网络抓取有很多包可以实现,比如:urllib、urllib2、httplib、httplib2。其中httplib、httplib2是专门处理与http相关的;而urllib、urllib2是借助于httplib、httplib2实现的,相当于在httplib、httplib2上又封装了一层来进行处理web数据。而urllib2是urllib的高版本,httplib2是httplib的高版本。

这里我给出一个学习python库的网址,里面有各种lib库的讲解:http://docs.python.org/library/index.html 。有兴趣的读者可以看一下。

由于最近在使用httplib2进行网上抓取数据,所以下面对httplib2进行介绍。

2.httplib2

(1)安装

httplib2的安装:首先下载python的httplib2的安装包,下载地址为:http://code.google.com/p/httplib2/downloads/list;其次, 在dos窗口下进入httplib2的解压目录,执行命令:python setup.py install 。 即完成安装。

(2)使用讲解

下面再给出一个httplib2的学习地址,是httplib2的一个wiki,里面有几个httplib2的简单例子:http://code.google.com/p/httplib2/wiki/Examples

(a)下面给出第一个httplib2的例子,根据weibo用户的ID,抓取用户相关信息:

这个例子涉及到cookies的使用,下面简单介绍一下cookies的问题:

当用户抓取的网页需要登陆时,一般的登录页面只需要用户名和密码,此时我们可以采用username和password的python代码抓取网页。但是当用户频繁的登录抓取时,此时登录页面就需要输入用户名、密码和一个随机数字图片验证,图片验证无法屏蔽掉。此时,我们这里可以采用先获取用户登陆后的cookies,再以http的get请求的方式向用户发送headers(该headers包含用户登陆后的cookies信息),这样就避免了登陆。

获取cookies的方法我感觉有2种,一种是手工方式:通过fiddler软件来查看,当用户在浏览器里输入请求的网址时,通过fiddler就可以查看用户的http的响应,具体的过程如下面2张图片所示,第一张图片是fiddler查看cookies的header,第二张图是拷贝该header:





第二种获取cookies的方式,可以第一次通过用户名和密码向url发出请求,然后将请求后的response中的headers的cookies保存下来,下次就可以根据cookies去登录该url并获取页面内容。

好,cookies介绍完毕,咱们回来,下面给出第一个demo(通过cookie登陆获取微博页面,并抓取微博用户信息):

#!/usr/bin/python
# -*-coding:utf-8 -*-
import httplib2
import urllib2
import re #正则表达式模块

class WeiboClass: #定义一个weibo类

#获取指定url的网页内容
def get_content(self,url,headers,id):
http=httplib2.Http()
response,content=http.request(url+str(id),'GET',headers=headers)
#print url+str(id)
return content.decode('unicode-escape').encode('utf-8')

#判断weibo的用户是否是企业用户
def is_company(self,url,headers,id):
content=self.get_content(url,headers,id)
title=r'行业'
company_title=re.compile(title)
if company_title.search(content): #使用正则表达式对title进行匹配
return 1
else:
return 0

#获取用户的weibo信息:ID,主页url,昵称
def get_info(self, url,headers,id):
flag=self.is_company(url,headers,id)
content=self.get_content(url,headers,id)
if flag==0:  #如果用户是个人用户
#print content
#微博ID
id_flag=r'\$CONFIG\[\'oid\'\] = \'([0-9].+?)\';'
id_re=re.compile(id_flag)
id_regx=id_re.search(content)
id=id_regx.group(1)
print id

#微博url
url_flag=r'<meta http-equiv="mobile-agent" content="format=xhtml;" url="weibo.cn/(.+?)\?'
url_re=re.compile(url_flag)
url_regx=url_re.search(content)
url_0=url_regx.group(1)
url='http://weibo.com/'+url_0
print url

#昵称
name_flag='<div class="name clearfix">.+?<div class="left">(.+?)<'
name_re=re.compile(name_flag,re.S)
name_regx=name_re.search(content)
name=name_regx.group(1)
name=name.decode('utf-8').encode('GBK')
print name

def main():
headers={"cookie":'NSC_wjq_xfjcp.dpn_w3.6_w4=ffffffff0941137b45525d5f4f58455e445a4a423660; SUS=SID-1806453925-1342851885-XD-hgrjc-03210d75ca203f3ad0d57666a05ae49d; SUE=es%3Deabd0a14bc6e6123c5c4d058d9a2c96f%26ev%3Dv1%26es2%3Db50ae59b82b457a1ba54b2b7708fbb5b%26rs0%3DdP4RVYzORRwV64PFw6wRdNGBk0HP47V8C5SXUp%252F7Q9K2RcduYt4ECQbEDNZk%252Bs8GHDpW5wk%252B3%252FmYKP12zIyQbD1bUMd2wNBgdRX45p2rygizXgHMjH%252FFnU53HJFC2OfSvEHZADJkZD%252BTdLHidgoyy4maajVHi%252B%252B1en0zIKIf3mo%253D%26rv%3D0; SUP=cv%3D1%26bt%3D1342851885%26et%3D1342938285%26d%3Dc909%26i%3D4dc3%26us%3D1%26vf%3D0%26vt%3D0%26ac%3D0%26uid%3D1806453925%26user%3Dqipeng.sdsy%2540163.com%26ag%3D4%26name%3Dqipeng.sdsy%2540163.com%26nick%3D%25E4%25B8%2580%25E5%258C%25B9%25E5%259C%25A8%25E8%25B7%25AF%25E4%25B8%258A%25E7%259A%2584%25E9%25A9%25AC%26fmp%3D%26lcp%3D; SSOLoginState=1342851885; ads_ck=1; UOR=hao.360.cn,weibo.com,spr_web_360_hao360_weibo_t001:1342851234578; _olympicMedalsTable=; ULV=1342851891453:53:53:53:3919105774821.296.1342851890484:1342851070843; SinaRot/u/1806453925=25; un=qipeng.sdsy@163.com; __utma=182865017.1486634646.1342787238.1342787238.1342787238.1; __utmz=182865017.1342787238.1.1.utmcsr=weibo.com|utmccn=(referral)|utmcmd=referral|utmcct=/u/1806453925; myuid=1806453925; wvr=4; ALF=1343135290; SinaRot/u/1806453925%3Fgid%3D201108260278357557=77; SinaRot/u/1806453925%3Fc%3Dspr_web_360_hao360_weibo_t001=59; _s_tentry=-; Apache=3919105774821.296.1342851890484; SINAGLOBAL=3919105774821.296.1342851890484; BAYEUX_BROWSER=4e70u68yfp1x9xreh4wbcw2jfhs; JSESSI\
ONID=3nvoas5ykh0e'}
url='http://weibo.com/'
print headers
page = WeiboClass()
page.get_info(url,headers,1842764911)

if __name__ == "__main__":
main()

(b)下面给出httplib2的第二个例子

#!/usr/bin/python
# -*-coding:utf-8 -*-
import sys;
import os;
import urllib2;
import httplib2;
import Cookie;
import random;
import re;
import time;
from urllib import urlencode;
import hashlib;
import datetime;
import socket;

#httplib2.debuglevel=1;

def error_log(log):
sys.stderr.write("%s\n" % (log));

class ErrorCode:
Succ         = 0;
Unknown      = -1; # 未知原因

class SimBrowser :
socket.setdefaulttimeout(10); # 超时限制 10秒
UserAgent="";
cookie=None;
httplink = httplib2.Http();
httplink.follow_redirects = False;
hostname="";
def __init__(self, cookie, UserAgent="Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727)"):
self.cookie = cookie;
self.UserAgent = UserAgent;

def gen_cookie_str(self):
cookiestr = '; '.join(self.cookie.output(attrs=[], header="").split('\r\n'));
if len(cookiestr) <= 0:
return "";
else:
return cookiestr;
def prepare_request_header(self, header):
newheader = {};
cookiestr = self.gen_cookie_str();
if len(cookiestr) > 0:
newheader['Cookie'] = cookiestr;
# set agent
newheader['User-Agent'] = self.UserAgent;

# replace or append user specified values in header
for key in header.keys():
newheader[key] = header[key];
return newheader;

# maintain cookies
def maintain_cookie(self, response_header):
if 'set-cookie' in response_header:
self.cookie.load(response_header['set-cookie']);

def get_redirect_url(self, prevurl, res):
if 'location' not in res:
error_log('no location in res');
return "";

location = res['location'];
if len(location) <= 0:
error_log('location length is zero');
return "";

# check location contain fullpath of target
if location.find("http://") != 0:
p = re.compile(r"[(http://)]*[.\-_0-9A-Za-z]+");
m = p.match(prevurl);
if m != None:
host = m.group();
return host + location;
else:
error_log('cannot get host link');
host = "";
else:
return location;

def request(self, url, method="GET", headers={}, body="", follow_redirects=False):
newheaders = self.prepare_request_header(headers);
newurl = url;
newbody = body;
while (True):
try:
res, content = self.httplink.request(newurl, method=method, headers=newheaders, body=newbody);
self.maintain_cookie(res);
except Exception , what:
try:
res, content = self.httplink.request(newurl, method=method, headers=newheaders, body=newbody);
self.maintain_cookie(res);
except Exception , what:
try:
res, content = self.httplink.request(newurl, method=method, headers=newheaders, body=newbody);
self.maintain_cookie(res);
except Exception , what: # 访问获取 三次 不成功返回失败
res='';
content='';
break;

# check redirects
if follow_redirects==False:
break;
elif res.status in(300, 301, 302):
prevurl = newurl;
newheaders = self.prepare_request_header({});
newurl = self.get_redirect_url(prevurl, res);
body = "";
method="GET";
if len(url) > 0:
continue;
else:
sys.stderr.write("Error:failed to get redirect location\n");
break;
else:
break;
return res, content;
def main():
cookie = Cookie.SimpleCookie();
sim = SimBrowser(cookie);

aurl='http://s.weibo.com/weibo/computer&Refer=STopic_box';
myCookie = ('UOR=,weibo.com,; myuid=1369574593; un=zhaolianxiang@126.com; \
un=zhaolianxiang@126.com; wvr=4; __utma=15428400.565128140.1342666019.13426660\
19.1342666019.1; __utmz=15428400.1342666019.1.1.utmcsr=blog.sina.com.cn|utmccn\
=(referral)|utmcmd=referral|utmcct=/s/blog_84313fa001010n90.html; NSC_wjq_xfjc\
p.dpn_w3.6_w4=ffffffff0941010945525d5f4f58455e445a4a423660; SSOLoginState=1342\
867370; _s_tentry=login.sina.com.cn; Apache=9964875415898.86.1342867464581; UL\
V=1342867464624:7:7:4:9964875415898.86.1342867464581:1342514299919; SUE=es%3D6\
c406ebb66f15ce0e5b852efa908d728%26ev%3Dv1%26es2%3D658950facb6ad8c9c8627639f31a\
61de%26rs0%3Df6AWRRwcy3r7HJ7y1mdnQR5icnbFHj6Qt%252F6Og2%252FaDBwMtLGJbQhanphxu\
EWsXCI1CPdl1yhB%252BHNobWvCmQPmF0xjrJhrvxCWAoiiE7D9cPDXQVOvlQPDsAopv10Un5DDuES\
%252FZcPXtwnaCYnD5hcMAoDinTKgBxbeZ%252FBSiLzvEdQ%253D%26rv%3D0; SUP=cv%3D1%26b\
t%3D1343009147%26et%3D1343095547%26d%3Dc909%26i%3D3d45%26us%3D1%26vf%3D0%26vt%\
3D0%26ac%3D1%26uid%3D1842764911%26user%3Dzhaolianxiang%2540126.com%26ag%3D4%26\
name%3Dzhaolianxiang%2540126.com%26nick%3DSean%26fmp%3D%26lcp%3D2011-12-25%252\
012%253A59%253A36; SUS=SID-1842764911-1343009147-XD-cyk5d-215c6a72f1b3a340c301\
533e2b4ce49d; ALF=1343095212; ads_ck=1; SinaRot/z/zhaolianxiang=44; SINAGLOBAL\
=9964875415898.86.1342867464581; _olympicMedalsTable=; USRHAWB=usrmdins213_206');
headers={'Cookie':myCookie,'Content-Type':'application/x-www-form-urlencoded'};
#    print "myCookie:",myCookie;
#    body={'path':'GET/material','userid':self.BaiduId,'token':self.token,'params':'{"level":"useracct","fields":["wregion","wbudget","userstat"]}'};
#以http的方式向aurl发出get请求,同时将http的头headers发送过去
#这个headers包含了用户的登陆的cookies信息。cookies的获取可以通过fiddler软件来查看。
res, content = sim.request(aurl,'GET', headers=headers);
print "res:",res  #输出http的响应response
#将获取的网页内容先解码,再以utf-8的形式编码
print "content:",content.decode('unicode-escape').encode('utf-8');

if __name__ == "__main__":
main();

注:上面的两个程序中的cookies均已失效,需要您通过我上面介绍的方法替换cookies中的内容。

(3)总结

使用cookie登陆抓取指定页面,可以像下面这样,这是一个最简洁的代码

#!/usr/bin/python
# coding:utf-8
import httplib2
import urllib2
import re #正则表达式模块

class PageClass:

#获取指定url的网页内容
def get_page(self,url,headers):
http=httplib2.Http()
response,content=http.request(url,'GET',headers=headers)
#return content.decode('unicode-escape').encode('utf-8')
return content.decode('unicode-escape').encode('utf-8')

def main():
headers={"cookie":'your cookie'}
url = 'http://fengchao.baidu.com'
#print headers
page = PageClass()
content = page.get_page(url,headers)
print content

if __name__ == "__main__":
main()
只需要输入url和headers就可以抓取到指定的页面。这里需要输入你自己的cookie。上面的代码执行后的结果如下,可以看到我们抓取到了内容:



3.XPath

将抓取到的页面源码,如何进行解析,一般采用下面三种技术:

(1)lxml的xpath:基于XML的语义进行解析的(推荐)。

(2)正则表达式(RE):基于纯文本的处理。

(3)纯字符串处理(不推荐)

总结:RE对付简单的页面没有问题,如果页面结构复杂度较高的时候,建议采用xpath,因为此时设计一个合适的RE pattern可能会远比写一个xpath要复杂。

lxml的下载地址:http://pypi.python.org/pypi/lxml/2.3

上面介绍的httplib2的2个示例均是通过RE(正则表达式)来进行页面解析的。下面我们介绍XPath来进行页面解析想要的内容。

XPATH基本上是用一种类似目录树的方法来描述在XML文档中的路径。比如用“/”来作为上下层级间的分隔。第一个“/”表示文档的根节点(注意,不是指文档最外层的tag节点,而是指文档本身)。比如对于一个HTML文件来说,最外层的节点应该是”/html”。同样的,“..”和“.”分别被用来表示父节点和本节点。

XPATH返回的不一定就是唯一的节点,而是符合条件的所有节点。比如在HTML文档里使用“/html/head/scrpt”就会把head里的所有script节点都取出来。

为了缩小定位范围,往往还需要增加过滤条件。过滤的方法就是用“[”“]”把过滤条件加上。比如在HTML文档里使用“/html/body/div[@id='main']”,即可取出body里id为main的div节点。其中@id表示属性id,类似的还可以使用如@name, @value, @href, @src, @class…. 。

而函数text()的意思则是取得节点包含的文本。比如:<div>hello<p>world</p>< /div>中,用”div[text()='hello']“即可取得这个div,而world则是p的text()。

函数position()的意思是取得节点的位置。比如“li[position()=2]”表示取得第二个li节点,它也可以被省略为“li[2]”。

不过要注意的是数字定位和过滤条件的顺序。比如“ul/li[5][@name='hello']”表示取ul下第五项li,并且其name必须是hello,否则返回空。而如果用“ul/li[@name='hello'][5]”的意思就不同,它表示寻找ul下第五个name为”hello“的li节点。

此外,“*”可以代替所有的节点名,比如用”/html/body/*/span”可以取出body下第二级的所有span,而不管它上一级是div还是p或是其它什么东东。

而 “descendant::”前缀可以指代任意多层的中间节点,它也可以被省略成一个“/”。比如在整个HTML文档中查找id为“leftmenu”的 div,可以用“/descendant::div[@id='leftmenu']”,也可以简单地使用“ //div[@id='leftmenu']”。

至于“following-sibling::”前缀就如其名所说,表示同一层的下一个节点。”following-sibling::*”就是任意下一个节点,而“following-sibling::ul”就是下一个ul节点。

下面给出一个示例,其功能为提取百度首页搜索框上面的导航条的内容:

#该程序是对百度的首页进行分析,并提取出其搜索框上面的导航条
import httplib2
import urllib2
import re
from lxml import etree

def main():
http = httplib2.Http()
response,content = http.request("http://www.baidu.com",'GET')
print "response:",response
print "content:",content

tree = etree.HTML(content)

#上面的注释为要查找的部分html
#<p id=nv><a href=http://news.baidu.com>新闻</a><b>网页</b>
#<a href=http://tieba.baidu.com>贴吧</a><a href=http://zhidao.baidu.com>知道</a>
#<a href=http://mp3.baidu.com>MP3</a><a href=http://image.baidu.com>图片</a>
#<a href=http://video.baidu.com>视频</a><a href=http://map.baidu.com>地图</a></p>

#下面开始查找id为nv的p标签下的所有<a>的href值
hyperlinks = tree.xpath(u'//p[@id="nv"]/a/@href')
print "hyperlinks:",hyperlinks
for hyperlink in hyperlinks:
print "hyperlink:",hyperlink

#查找id为nv的p标签下的所有<a>节点
a_nodes = tree.xpath(u'//p[@id="nv"]/a')
print "a_nodes_length:",len(a_nodes)
for a_node in a_nodes:
print "<a>:",a_node.text,a_node.attrib['href']
print "\n"

#通过正则表达式查找<p id="nv">的标签内容,匹配的内容为正则表达式中的"()"内的内容
name_flag='<p id="nv">(.+?)</p>'
name_re=re.compile(name_flag,re.S)
name_regx=name_re.search(content)
print name_regx
name=name_regx.group(1)
print "name:",name

if __name__ == "__main__":
main()
其执行结果为:



下面对其进行分析:

首先,对www.baidu.com进行http的get请求,然后将相应的页面结果进行分析。这里我使用了3种方式进行页面分析,前2种方式为使用xpath提取,第3种方式为通过正则表达式匹配提取。程序中有详细的注释。

4.HTMLParser

该模块是用来解析HTML元素的。可以从HTML中筛选出指定的标签。下面给出一个例子,读取百度首页www.baidu.com中的所有链接,并打印出来。

import HTMLParser
import urllib
import sys

#定义HTML解析器
class parseLinks(HTMLParser.HTMLParser):
#该方法用来处理开始标签的,eg:<div id="main">
def handle_starttag(self, tag, attrs):
if tag == 'a':  #如果为<a>标签
#name为标签的属性名,如href、name、id、onClick等等
for name,value in attrs:
if name == 'href': #这时选择href属性
print "name_value: ",value  #href属性的值
print "first tag:",self.get_starttag_text() #<a>标签的开始tag
print "\n"

if __name__ == "__main__":
#创建HTML解析器的实例
lParser = parseLinks()
#打开HTML文件
lParser.feed(urllib.urlopen("http://www.baidu.com").read())
lParser.close()
但调用feed函数时,会自动调用handle_starttag函数,这里的handle_starttag函数是对原函数的重写。handle_starttag(self,tag,attrs)中的参数tag是标签的名字;参数attrs是一个(name,value)键值对,是通过查找到tag的<>括号来确定的,其中name是tag的<>中的属性名,value是去除引号后的值。

上面程序的执行结果如下:(下面的结果只是部分的屏幕截图)



5.正则表达式(RE)

由于上面涉及到很到正则表达式的匹配问题。下面对Python中的RE使用进行简单的演示和说明。下面给出一段,我测试用的code:

import re

text = "JGood is a handsome booy, he is cool, clever, and so on..."
regex1 = re.match(r"\w*oo\w*", text)
if regex1:
print "regex1:" , regex1
print "result1:" ,regex1.group(0)
else:
print 'not match'
print "\n"

regex2 = re.compile(r'(\w*oo\w*)')
print "result2:" , regex2.findall(text)
print "\n"

regex3 = re.compile(r'(\w*oo\w*).+?(\w*eve\w*).*')
regex3_result = regex3.search(text)
if regex3_result:
print "regex3:", regex3
print " result3:",regex3_result.group(1)," ",regex3_result.group(2)
else:
print 'not match'
程序的运行结果:



下面简单的讲解一下:

regex1是通过match方法进行匹配,match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;regex2是通过findall,查找所有满足的匹配;regex3是通过search匹配整个字符串,直到找到一个匹配。

对于regex3_result.group(1),regex3_result.group(2)是什么意思呢,我搞了半天才弄明白。group(i)表示匹配的正则表达式"()"中的内容。如regex3_result.group(1)表示匹配的正则表达式中第一个()内的内容,regex3_result.group(2)表示匹配的正则表达式中第二个“()”的对应的字符串。group(0)表示整个表达式。group是从左向右计数的,从1开始。组可以被嵌套。计数的数值可以通过从左到右计算打开的括号数来确定。

再给出一个小例子,一目了然:

#!python
>>> p = re.compile('(a(b)c)d')
>>> m = p.match('abcd')
>>> m.group(0)
'abcd'
>>> m.group(1)
'abc'
>>> m.group(2)
'b'


6.如何判断网络连接

有的软件如果需要访问网络,这时我们就需要定时地检测网络的连接,下面的代码给出了如何检测网络连接:当连接时返回1;当网络断掉时返回0。原理就是通过http去请求网页,如果能够请求到则网络正常,如果请求出现异常则网络断掉。

def check_network():
import httplib2
try:
http = httplib2.Http()
resp, content = http.request("http://www.baidu.com")
except:
return 0
return 1
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: