Python网页爬虫(一)

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

很多时候我们想要获得网站的数据,但是网站并没有提供相应的API调用,这时候应该怎么办呢?还有的时候我们需要模拟人的一些行为,例如点击网页上的按钮等,又有什么好的解决方法吗?这些正是python和网页爬虫的应用场景。python是一种动态解释性语言,简单的语法和强大的库支持使得python在数据收集、数据分析、网页分析、科学计算等多个领域被广泛使用。

本文主要总结一下如何用python自己写一个简单的爬虫,以及可能出现的问题与解决方法。

首先介绍一下大概的思路,首先需要在程序中连接网站并发送GET请求得到html文件,然后需要解析html文件,根据某种规律将需要的信息提取出来,然后用合适的方式处理数据并保存。

(一)python中用于http连接的库——urllib2

首先放上python文档的链接,英文好的同学可自由阅读:https://docs.python.org/2/library/urllib2.html?highlight=urllib2#module-urllib2

这个库是python用来解析URL主要是HTTP的主要工具,而且提供了对身份认证、重定向、cookie等的支持,具体用法可以仔细的读文档,这里作为最简单的场景,我介绍如何打开一个URL并得到对应的HTML页面。只要使用一个函数:

urllib2.urlopen(url[, data[, timeout[, cafile[, capath[, cadefault[, context]]]]])

1、函数的参数url可以是一个string也可以是一个request对象,如果提供了参数列表中data的值(dict类型)那么函数将发送POST请求,默认情况是发送GET。另外,很多人说没法设置连接的timeout,然后提供了一堆类似修改socket全局timeout的方法,其实这里可以直接设定的嘛!

2、函数的返回值。函数返回一个类文件的东西,which means,函数返回的值可以当一个文件类来操作,只是多了三个方法:

geturl()返回这个html对应的urlinfo() 返回这个html文件的元信息例如headersgetcode()返回网站的返回的状态码,如200表示OK,404表示not found之类。3、函数抛出的异常:URLError例如发生无网络连接这种事情。socket.timeout如果你设置了timeout参数,超时后便会抛出此异常。

所以这里的代码可以这样写:

1importurllib22import socket3defgethtml(url):4try:5f = urllib2.urlopen(url,timeout=10)6data =f.read()7exceptsocket.timeout, e:8data =None9PRint"time out!"10with open("timeout",'a') as log:11log.write(url+'\n')12excepturllib2.URLError,ee:13data =None14print"url error"15finally:16returndata

这样就可以得到对应URL的网页的html代码了(但是在具体应用你可能会碰到蛋疼的问题,我碰到的会列在下面)

(二)对html的解析

获得了网页源代码后我们需要从里面提取出接下来要爬取的URL或者我们需要的数据,这就需要对html解析。2.7版本的python中,内置的解析类是HTMLParser,库文档:https://docs.python.org/2/library/htmlparser.html?highlight=htmlparser

其实,你要做的全部,就是继承这个类,然后把里面的接口函数实现,他看起来是这样子的:

1fromHTMLParserimportHTMLParser23#create a subclass and override the handler methods4classMyHTMLParser(HTMLParser):5defhandle_starttag(self, tag, attrs):6print"Encountered a start tag:", tag7defhandle_endtag(self, tag):8print"Encountered an end tag :", tag9defhandle_data(self, data):10print"Encountered some data :", data

例如对于一个最简单的html文件:

1<html>2<head>3<title>Test</title>4</head>5<body>6<h1>Parse me!</h1>7</body>8</html>

当程序遇到每一个标签的开始时便会调用handle_starttag函数,遇到标签结束就会调用handle_endtag,遇到标签之间的数据就会调用handle_data可以结合最后贴出的示例代码进一步理解。想要解析这个文件只要如下写:

#例如借用上面的gethtml函数defparse():

html= gethtml("http://baidu.com",timeout=10)

parser=MyParser()

parser.feed(html)

parser.close()

调用feed函数把html放入缓冲区,调用close函数强制parser开始解析html

(三)问题与解决方法:

1、得到的html文档是乱码

这个问题可能有很多原因引起,最主要的两个是:网页的编码方式和你的解码方式不匹配。关于编码和解码推荐读一下这个博客:http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386819196283586a37629844456ca7e5a7faa9b94ee8000

这时候你会明白编码问题确实导致了文件的不可读。所以你要正确获得网页的编码方式,然后调用对应的解码器,代码可能是这样的:

f = urllib2.urlopen(url,timeout=10)

data=f.read()#decode the htmlcontentType = f.headers.get('Content-Type')ifcontentType.find("gbk"):

data= unicode(data,"GBK").encode("utf-8")elifcontentType.find("utf-8"):pass

当然,编码方式可能多种,不止GBK

2、还是乱码

如果还是乱码,可能你依然没有选对解码器,也可能是网页被压缩过了,这时候你要先把网页解压缩,然后正确解码,解码的代码可能是这样的:

1importStringIO, gzip23f = urllib2.urlopen(url,timeout=10)4#consider some html is compressed by server with gzip5isGzip = f.headers.get('Content-Encoding')6ifisGzip:7compressedData =f.read()8compressedStream =StringIO.StringIO(compressedData)9gzipper = gzip.GzipFile(fileobj=compressedStream)10data =gzipper.read()11else:12data = f.read()

3、调用urlopen函数后程序卡住

这个的主要问题是没有设置timeout参数,导致网络不通畅时引起urlopen函数无限等待,程序卡死,所以,设置timeout=10例如,单位是秒,并处理抛出的socket.timeout异常,就可以避免这个问题了。

4、被服务器拉黑

如果你对某个域名访问速度太快,就可能被服务器认定为潜在的DDos攻击,IP就会被封一段时间。解决方法是不要过快的访问可以使用sleep语句参数单位为秒。

importtimeforurlinurllist:

f= urllib2.urlopen(url,timeout=10)

...

time.sleep(1)

(四)小爬虫

自己写了一个小爬虫,目的是爬取www.gaokao.com网上所有本科大学历年的分数线等数据,其中schoollist文件存储了所有大学的ID号,你可以手写一个list代替例如:

schoollist = ['1','2','3']

代码就暴力贴了:

#!/usr/bin/env python#-*- coding: utf-8 -*-__author__='holmes'fromHTMLParserimportHTMLParserimporturllib2importStringIO, gzipimportthreadingimportosimporttimeimportsysimportsocket#hard codeLOCATION = ("北京","天津","辽宁","吉林","黑龙江","上海","江苏","浙江","安徽","福建","山东","湖北","湖南","广东","重庆","四川","陕西","甘肃","河北","山西","内蒙古","河南","海南","广西","贵州","云南","西藏","青海","宁夏","新疆","江西",)#hard codeSUBJECT = ("理科","文科",)'''Rules for URL

http://college.gaokao.com/school/tinfo/%d/result/%d/%d/ %(schoolID,localID,subID)

where localID from 1 to 31

where subID from 1 to 2'''SEED="http://college.gaokao.com/school/tinfo/%s/result/%s/%s/"SID="schoolID"#file name contains school IDsclassSpiderParser(HTMLParser):def__init__(self, subject=1, location=1):

HTMLParser.__init__(self)

self.campus_name=""self.subject= SUBJECT[subject - 1]

self.location= LOCATION[location - 1]

self.table_content=[[], ]

self.line_no=0

self.__in_h2=False

self.__in_table=False

self.__in_td=Falsedefhandle_starttag(self, tag, attrs):iftag =="h2":

self.__in_h2=Trueiftag =="table":

self.__in_table=Trueiftag =="tr"andlen(attrs) !=0:ifself.__in_table:

self.table_content[self.line_no].append(self.campus_name)

self.table_content[self.line_no].append(self.subject)

self.table_content[self.line_no].append(self.location)iftag =="td":ifself.__in_table:

self.__in_td=Truedefhandle_endtag(self, tag):iftag =="h2":

self.__in_h2=Falseiftag =="table":

self.__in_table=Falseiftag =="tr":ifself.__in_table:

self.line_no+= 1self.table_content.append([])iftag =="td":ifself.__in_table:

self.__in_td=Falsedefhandle_data(self, data):ifself.__in_h2:

self.campus_name=dataifself.__in_td:

self.table_content[self.line_no].append(data)defgetschoolID():

with open(SID, mode='r') as rf:

idlist=rf.readlines()printidlistreturnidlistdefgethtml(url):try:

f= urllib2.urlopen(url,timeout=10)#consider some html is compressed by server with gzipisGzip = f.headers.get('Content-Encoding')ifisGzip:

compressedData=f.read()

compressedStream=StringIO.StringIO(compressedData)

gzipper= gzip.GzipFile(fileobj=compressedStream)

data=gzipper.read()else:

data=f.read()#decode the htmlcontentType = f.headers.get('Content-Type')ifcontentType.find("gbk"):

data= unicode(data,"GBK").encode("utf-8")elifcontentType.find("utf-8"):passexceptsocket.timeout, e:

data=Noneprint"time out!"with open("timeout",'a') as log:

log.write(url+'\n')finally:returndatadefparseandwrite((slID, lID, sID), wfile):try:

url= SEED %(slID, lID, sID)

html=gethtml(url)ifhtmlisNone:print"pass a timeout"returnparser=SpiderParser(sID, lID)

parser.feed(html)

parser.close()ifparser.line_no !=0:forlineinparser.table_content:foriteminline:if"--"initem:

item="NULL"wfile.write(item+',')

wfile.write('\n')excepturllib2.URLError, e:print"url error in parseandwrite()"raisedefthread_task(idlist, name):try:print"thread %s is start"%name

wf= open(name, mode='w')

wf.write("大学名称,文理,省份,年份,最低分,最高分,平均分,录取人数,录取批次")forsIDinidlist:printname +":%s"%idlist.index(sID)

sID= sID.strip('\n')

i= 1.0forlocalIDinrange(1, 32):forsubIDinrange(1, 3):

parseandwrite((sID, localID, subID), wf)

sys.stdout.write("\rprocess:%.2f%%"% (i / 62.0 * 100))

sys.stdout.flush()

i+= 1.0time.sleep(1)excepturllib2.URLError:

with open("errorlog_"+ name,'w') as f:

f.write("schoolID is %s , locationID is %s ,subID is %s"%(sID, localID, subID))print"schoolID is %s ,locationID is %s ,subID is %s"%(sID, localID, subID)finally:

wf.close()

THREAD_NO= 1defmaster():

school=getschoolID()foriinrange(THREAD_NO):

path= os.path.join(os.path.curdir,"errorlog_"+str(i))ifos.path.exists(path):

with open("errorlog_"+ str(i),'r') as rf:

sID= rf.readline().split()[2]

start=school.index(sID)else:

start= len(school) / THREAD_NO *i

end= len(school) / THREAD_NO * (i + 1) - 1ifi == THREAD_NO - 1:

end= len(school) - 1t= threading.Thread(target=thread_task, args=(school[start:end],"thread"+str(i),))

t.start()print"start:%s \n end:%s"%(start, end)

t.join()print"finish"if__name__=='__main__':#thread_task(["1"],"test")master()#gethtml("http://www.baidu.com")

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航