站的爬虫实战
2014-12-02 20:04:31
标签:网站 爬虫 python import
版权声明:原创作品,如需转载,请与作者联系。否则将追究法律责任。
【前言】
1 #本脚本仅用于技术交流,请2 3 勿用于其他用途 4 5 6 #byRiver #Date:2014-12-0219:00:00 【需求说明】
以京东为示例,爬取页面的,获取页面中得数据:记录到data.txt;获取页面中得图片,保存下来。
1、list的url如下
2、商品详情页的url如下:
【技术说明】
1 使用了2 3 4 python的以下库 importos#检查文件是否存在等 fromHTMLParserimportHTMLParser#html的库,有坑:如果2.6python,可能悲剧 用于解析5 6 的7 importhttplib,re#发起http请求 8 importsys,json,datetime,bisect#使用了二分快速查找 fromurlparseimporturlparse#解析url,分析出url的各部分功能 fromthreadingimportThread#使用多线程 importsocket#设置httplib超时时间 【代码逻辑说明】
1、run(获取最终要的结果)
2、parseListpageurl:返回list的总共的页面数量
3、judgelist:判断该list是否已经爬取完毕了,第一个list中的所有url、最后list的所有url都爬取完毕了,那么久说明list的所有page爬取完毕了(实际上是一种弱校验)
4、getfinalurl_content:如果list没爬取完毕,每个list爬取,解析list中得每个html(判断html是否爬取过),获得内容和img
【坑说明】
1、需要设置超时时间,和重试,否则爬取一个url卡住的时候,整个线程都悲剧了。
2、有编码的坑,如果页面是gb2312的编码,需要转换为utf-8的编码:httprestmp.decode('gbk').encode('utf-8')
3、parser.feed的内容,如果存在一些特殊字符,可能需要替换,否则解析出来会莫名不对
4、图片保存,根据url获取前面两个数字,保存。以免一个目录下保存了过多的图片。
【执行结果】
1、console输出
2、data.txt存储解析出来的内容
3、judegurl.txt(保存已经爬取过的url)
4、图片(下载的图片)
【代码详情】
1 2 3 #-*-coding:utf-8-*- __author__='River' #本脚本仅用于技术交流,请勿用于其他用途 4 5 #byRiver 6 7 8 #Date:2014-12-0219:00:00 importos#创建文件 html的库,有坑:如果2.6的fromHTMLParserimportHTMLParser#用于解析9 1python,可能悲剧 0 11 importhttplib,re#发起http请求 importsys,json,datetime,bisect#使用了二分快速查找 url,分析出url的各部分功能 1fromurlparseimporturlparse#解析112 fromthreadingimportThread#使用多线程 3 importsocket#设置httplib超时时间 #htmlparser的使用简介 intt方法:需要使用到得属性 handle_starttag,处理你想分析的tag的具体操作 #定义4 1#定义5 16 #定义handle_data,遇到你定义的情况,获取相应标签的data #定义你获取最终返回的各种数据 1classListPageParser(HTMLParser): 7 18 def__init__(self): self.handledtags=['a'] 1self.processing=None 9 self.flag='' 22self.link='' self.setlinks=set()##该list页面中包含的每个商品的url,定义为set,0 主要是为了使用其特性:去重 1 2self.pageNo=1 2 23 self.alldata=[] HTMLParser.__init__(self) 2defhandle_starttag(self,tag,attrs): 4 pattern=re.compile(r'^[0-9]{2,}') 22#attrs是属性的list,每个属性(包含key,value)又是一个元组 #已上为例子:判断了该list的长度为3(其他的a标签就被过滤了) 和不想要的url的区别 5 iftaginself.handledtagsandlen(attrs)==3:#非常关键的是,找出你想的6 2url7 28 #print\"debug:attrs\ self.flag='' 2self.data='' 9 self.processing=tag 33fortarget,hrefinattrs:#非常关键的是,找出你想的url和不想要的url的区别 pattern2,说明是我url 0 ifpattern2.match(href):#再加一层判断,如果匹配上1 3们想要的2 33 else: pass 1 是由规则的:cat=737%2C794%2C798&page=10&JL=6_0_0,3#1、长度为4 #2,href335 所以,以下代码就出来了 iftaginself.handledtagsandlen(attrs)==1: self.flag='' 6 3self.data='' 7 38 self.processing=tag forhref,urlinattrs:#非常关键的是,找出你想的url和不想要的url的区别 3#print'debug:attrs',attrs 9 40 ifpattern3.match(url): #print'debug:url',url 4self.lasturl=url 1 else: 44pass defhandle_data(self,data): data,就pass把 2 ifself.processing:#去掉空格 3 4pass#其实这里我们根本没使用获取到得4 45 else: pass 4defhandle_endtag(self,tag): 6 iftag==self.processing: 44self.processing=None defgetlinks(self): 7 returnself.setlinks 8 4defgetlasturl(self): 9 50 returnself.lasturl #FinallPageParser的定义过程参考上个parser,关键是怎样分析页面,最终5写出代码,并且验证,这里就不详细说了 1 classFinallPageParser(HTMLParser): 55def__init__(self): self.handledtags=['div','h1','strong','a','del','div','img','li','s2 pan','tbody','tr','th','td','i'] 3 5self.processing=None 4 55 self.title='' self.jdprice='' 5self.refprice='' 6 self.partimgs_show=set()#展示图片 55self.partimgs=set()#详情图片 self.partdetail={}#商品详情,参数等 7 self.specification=[]#规格参数 8 5self.typeOrsize=set()#尺码和类型 9 60 self.div='' self.flag={} 6self.flag['refprice']='' 1 62 self.flag['title']='' self.flag['jdprice']='' 6self.flag['typeOrsize']='' 3 self.flag['partimgs']='' 66self.flag['partdetail']='' self.flag['specification']='' 4 self.flag['typeOrsize']='' 5 6self.link='' 6 67 self.partslinks={} HTMLParser.__init__(self) 6defhandle_starttag(self,tag,attrs): 8 self.titleflag='' 67self.flag['refprice']='' self.flag['title']='' 9 self.flag['jdprice']='' 0 7self.flag['typeOrsize']='' 1 72 self.flag['partimgs']='' self.flag['partdetail']='' 7self.flag['specification']='' 3 self.flag['typeOrsize']='' 77iftaginself.handledtags: self.data='' 4 self.processing=tag 5 7iftag=='div': 6 77 forkey,valueinattrs: self.div=value#取出div的name,判断是否是所需要的图片等元素 7iftag=='i': 8 self.flag['typeOrsize']='match' 78iftag=='a'andlen(attrs)==2: tmpflag=\"\" 9 forkey,valueinattrs: 0 8ifkey=='href'and 1 82 tmpflag=\"first\" ifkey=='title'andvalue!=\"\": 8tmpflag=tmpflag+\"second\" 3 84 iftmpflag==\"firstsecond\": self.flag['typeOrsize']='match' 8iftag=='h1': 5 self.flag['title']='match' 88iftag=='strong'andlen(attrs)==2: fortmpclass,idinattrs: 6 ifid=='jd-price': 7 8self.flag['jdprice']='match' 8 89 iftag=='del': self.flag['refprice']='match' 9iftag=='li': 0 self.flag['partdetail']='match' 99iftag=='th'ortag=='tr'ortag=='td':#++++++++############################################879498.htmltd中有br的只取到第一个,需要把
1 喜欢为“” 2 9self.flag['specification']='match' 3 94 iftag=='img': imgtmp_flag='' 9imgtmp='' 5 forkey,valueinattrs: 99if(key=='src'orkey=='data-lazyload'): imgtmp=value logo 6 ifkey=='width':############可能还有7 9ifre.search(r'^\\d{1,9}$',value): 8 99 ifint(value)<=160: imgtmp_flag='no' 1break 0ifself.div==\"spec-items\"andimgtmp!='': 0 10imgtmp=re.compile(\"/n5/\").sub(\"/n1/\ self.partimgs_show.add(imgtmp) elifimgtmp_flag!='no'andimgtmp!='': 1 1defhandle_data(self,data): 01ifself.processing: self.data+=data 2 0ifself.flag['title']=='match':#获取成功 3 10self.title=data ifself.flag['jdprice']=='match': self.jdprice=data.strip() 4 1ifself.flag['typeOrsize']=='match': 01ifself.flag['refprice']=='match': self.refprice=data.strip() 5 ',data):#获取成功 0ifself.flag['partdetail']=='match'andre.search(r':6 keytmp=data.split(\":\")[0].strip() 10valuetmp=data.split(\":\")[1].strip() self.partdetail[keytmp]=valuetmp 7 1ifself.flag['specification']=='match'anddata.strip()!=''anddata.str0ip()!='主体': 8 10else: pass defhandle_endtag(self,tag): 9 1iftag==self.processing: 11self.processing=None defgetdata(self): 0 1return{'title':self.title,'partimgs_show':self.partimgs_show,'jdpri1 ce':self.jdprice,'refprice':self.refprice,'partimgs':self.partimgs,11'partdetail':self.partdetail,'specification':self.specification,'typeOrsize':self.typeOrsize} httpread,用于发起http的get请求,返回http的获取内容 2 1#定义方法3 111#这也是代码抽象的结果,如若不抽象这块代码出来,后续你回发现很多重复的写这块代码 defhttpread(host,url,headers): httprestmp='' 4 1try: 11conn=httplib.HTTPConnection(host) conn.request('GET',url,None,headers) 5 1httpres=conn.getresponse() 6 httprestmp=httpres.read() 11exceptException,e: conn=httplib.HTTPConnection(host) 7 1conn.request('GET',url,None,headers) 11httpres=conn.getresponse() httprestmp=httpres.read() 8 1printe 9 finally: 12ifconn: conn.close() sendhttp,调用httpread,获取结果并替换编码(gbk换为utf-8),0 1returnhttprestmp 2#定义方法1 12并保存到文件中(以免下次再去下载页面,这样就节省了时间) # http头部,很多网站对于你不携带User-Agent及Referer等情况,是defsendhttp(url,host,savefile): 2 1#定义213 不允许你爬取。 #具体的http的头部有些啥信息,你可以看chrome,右键审查元素,点击requestheader 2network,点击其中一个链接,查看4 headers={\"Host\":host, 12\"Content-type\":\"application/x-www-form-urlencoded;charset=UTF-8\ \"Accept\":\"text/html;q=0.9,image/webp,*/*;q=0.8\ 5 1} 2httprestmp='' 6 12try: httprestmp=httpread(host,url,headers) ifhttprestmp=='':# 7 1httprestmp=httpread(host,url,headers) 21ifhttprestmp=='':#重试2次 httprestmp=httpread(host,url,headers) 8 2exceptException,e: 9 try: 13httprestmp=httpread(host,url,headers) ifhttprestmp=='':# 2次 0 1httprestmp=httpread(host,url,headers) 3ifhttprestmp=='':#重试1 13httprestmp=httpread(host,url,headers) exceptException,e: printe 2 13printe ifre.search(r'charset=gb2312',httprestmp):#如果是gb2312得编码,就要utf-8(因为全局都使用了utf-8) 3 1转码为4 133httprestmp.replace(\"charset=gb2312\ try: httprestmp=httprestmp.decode('gbk').encode('utf-8')#有可能转码失败,try 所以要加上5 311exceptException,e:#如果html编码本来就是utf8或者转换编码出错的时候,就啥都不做,就用原始内容 printe 6 3try: 7 withopen(savefile,'w')asfile_object: 13file_object.write(httprestmp) file_object.flush() 8 1exceptException,e: 3printe 9 14returnhttprestmp #list的页面的解析方法 defparseListpageurl(listpageurl): 0 1urlobj=urlparse(listpageurl) 41ifurlobj.query: geturl=urlobj.path+\"\"+urlobj.query 1 4else: 2 geturl=urlobj.path 14htmlfile=\"html/list\"+geturl ifnot 3 1httpresult=sendhttp(geturl,urlobj.hostname,htmlfile) 4withopen(htmlfile)asfile: 4 14htmlcontent=file.read() parser=ListPageParser()#声明一个解析对象 html的内容feed进去 parser.feed(htmlcontent.replace('amp;',''))#将5 1#print'debug:htmlcontent',htmlcontent 41finalparseurl=parser.getlinks()#然后get数据即可 lastpageurl=parser.getlasturl() 6 4urlobj_lastpageurl=urlparse(lastpageurl) 7 14#print'debug:urlobj_lastpageurl',urlobj_lastpageurl totalPageNo='0' #printurlobj 8 1ifre.search(r'&',urlobj_lastpageurl.query): 41try: exceptException,e: 9 5print\"lastpageurl:\"+str(lastpageurl) 0 printe 15parseListpageurl_rult={'finalparseurls':finalparseurl,'totalPageNo':totalPageNo} 1 1ifparseListpageurl_rult['finalparseurls']!=\"\"andparseListpageurl_ru5lt['totalPageNo']!='': 2 15print else: print-%m-%d%H:%M:%S\")+\ 3 1returnparseListpageurl_rult 51#最终的html页面的解析方法:会使用到html得解析器FinallPageParser defparseFinallyurl(finallyurl): 4 5urlobj=urlparse(finallyurl) 5 geturl=urlobj.path 15htmlfiledir=\"html/finally/\"+geturl.split('/')[1][0:2] ifnot 6 1try: 5os.makedirs(htmlfiledir) 7 15exceptException,e: printe htmlfile=htmlfiledir+geturl 8 1ifnot 51httpresult=sendhttp(geturl,urlobj.hostname,htmlfile) ifhttpresult: 9 6print 0 else: 16print withopen(htmlfile)asfile: 1 1htmlcontent=file.read() 61parser=FinallPageParser() ##htmmparser遇到/>就表示tag结尾,所以必须替换,遇到
替换为BRBR,2 6否则会解析失败 3 htmlcontent=re.compile('
').sub('BRBR',htmlcontent) 16parser.feed(htmlcontent) finalparseurl=parser.getdata() 4 1iffinalparseurl: 6print 5 16else: print returnfinalparseurl 6 1#获取图片的方法 61defgetimg(imgdir,imgurl): imgobj=urlparse(imgurl) 7 6getimgurl=imgobj.path 8 imgtmppathlist=getimgurl.split('/') 16imgname=imgtmppathlist[len(imgtmppathlist)-1] ifnot 9 1try: 7os.makedirs(imgdir) 0 17exceptException,e: printe savefile=imgdir+\"/\"+imgname 1 1ifnot 71sendhttp_rult=sendhttp(getimgurl,imgobj.hostname,savefile) ifsendhttp_rult: 2 7print 3 else: 17print else: 4 1pass 7#获取价格 5 17defgetprice(pricedir,priceurl): priceobj=urlparse(priceurl) getpriceurl=priceobj.path+\"\"+priceobj.query 6 17pricename=\"price\" ifnot 7 1try: 7os.makedirs(pricedir) 8 17exceptException,e: printe savefile=pricedir+\"/\"+pricename 9 1ifnot 81sendhttp_rult=sendhttp(getpriceurl,priceobj.hostname,savefile) ifsendhttp_rult: 0 8print 1 else: 18print else: 2 1pass 8withopen(savefile)asfile: 3 18price_content=file.read() price_content=re.compile('cnp\\\\(\\\\[|\\\\]\\\\);').sub('',price_content) price_dic={\"id\":\"0\ 4 1ifre.search(r':',price_content): 81try: price_dic=json.loads(price_content)#以免数据格式不对悲剧 5 8exceptException,e: 6 printe 18return{\"jdprice\":price_dic['p'],'refprice':price_dic['m']} #获取最后页面的具体内容 7 1defgetfinalurl_content(partlists,listpageurl,finalparseurl): 8parseFinallyurl_rult=parseFinallyurl(finalparseurl) 8 18htmlname_tmp=urlparse(finalparseurl).path imgtopdir_tmp=\"img/\"+htmlname_tmp.split('/')[1][0:2] imgdir=imgtopdir_tmp+htmlname_tmp+\"/introduction\" 9 1imgshowdir=imgtopdir_tmp+htmlname_tmp+\"/show\" 91partdetail_tmp=\"\" forimgurlinparseFinallyurl_rult['partimgs']:#获取商品介绍的图片 0 9getimg(imgdir,imgurl) 1 19forimgshowurlinparseFinallyurl_rult['partimgs_show']:#获取展示图片 getimg(imgshowdir,imgshowurl) forkeyinparseFinallyurl_rult['partdetail'].keys(): 2 1partdetail_tmp=partdetail_tmp+key+\"$$\"+parseFinallyurl_rult['partde91tail'][key]+\商品介绍 specification_tmp=\"\" 3 9i=0 4 forspecification_varinparseFinallyurl_rult[\"specification\"]:#规格参19数 ifi==0: 5 1str_slip=\"\" 9elif(i%2==0andi!=0): 6 19str_slip=\ else: str_slip=\"$$\" 7 1specification_tmp=specification_tmp+str_slip+specification_var 91i=i+1 typeOrsize_tmp=\"\" 8 9fortypeOrsize_varinparseFinallyurl_rult['typeOrsize']: 9 typeOrsize_tmp=typeOrsize_tmp+\ 20pricedir=\"price/\"+htmlname_tmp.split('/')[1][0:2]+htmlname_tmp getprice_dic=getprice(pricedir,priceurl) 0 2parseFinallyurl_rult[\"jdprice\"]=getprice_dic['jdprice'] 0parseFinallyurl_rult[\"refprice\"]=getprice_dic['refprice'] 1 20#partlists[listpageurl]):商品分类 #finalparseurl,页面的url #parseFinallyurl_rult[\"title\"]):标题 2 2#parseFinallyurl_rult[\"jdprice\"]:京东的价格 02#parseFinallyurl_rult[\"refprice\"]:市场参考价格 #imgshowdir:商品展示的图片保存位置 的商品说明也是用图片的 3 0#imgdir:商品说明的图片保存位置:jd4 #partdetail_tmp:商品的详细信息 20#specification_tmp:商品的规则参数 #typeOrsize_tmp:商品的类型和尺寸 5 2returnstr(partlists[listpageurl]).strip()+\"\\"+finalparseurl.strip(02)+\"\\"+str(parseFinallyurl_rult[\"title\"]).strip()+\"\\"+str(parseFinallyurl_rult[\"jdprice\"]).strip()\\ 6 0+\"\\"+str(parseFinallyurl_rult[\"refprice\"]).strip()+\"\\"+imgshowdir7 .strip()+\"\\"+imgdir.strip()+\"\\"+partdetail_tmp.strip()+\"\\"+speci20fication_tmp.strip()+\"\\"+\\ typeOrsize_tmp.strip() url(查找快了,同时也不用反8 2#判断最后的页面(商品详情页)是否被爬取了 0defjudgeurl(url):#优化后,使用二分法查找9 21复读取文件了)。第一次加载judgeurl_all_lines之后,维护好此list,同时新增的url也保存到judgeurl.txt中 url=url+\"\\n\" 0 2globaljudgeurl_all_lines 12find_url_flag=False url_point=bisect.bisect(judgeurl_all_lines,url)#这里使用二分法快速是排序好的) 1 1查找(前提:list212 find_url_flag=judgeurl_all_linesandjudgeurl_all_lines[url_point-1]==url returnfind_url_flag list页面是否已经爬取完毕了 list中的所有url、最后list的所有url都爬取完3 2#判断4 211#这里的逻辑是:第一个毕了,那么久说明list的所有page爬取完毕了(实际上是一种弱校验)。 #调用了judgeurl得方法 页面的所有的html是否下载完毕,以此判断该类型是否处理完毕 defjudgelist(listpageurl,finallylistpageurl):#判断第一个、最后一个的5 2list126 judgelist_flag=True parseListpageurl_rult_finally=parseListpageurl(finallylistpageurl) url的列表 1finalparseurls_deep_finally=list(parseListpageurl_rult_finally['fin7 alparseurls'])#获取到最后的需要解析的21parseListpageurl_rult_first=parseListpageurl(listpageurl) finalparseurls_deep_first=list(parseListpageurl_rult_first['finalpaurl的列表 8 2rseurls'])#获取到最后的需要解析的9 221forfinalparseurlinfinalparseurls_deep_finally: #printfinalparseurl ifjudgeurl(finalparseurl): pass 0 22else: judgelist_flag=False 1 2break 2ifjudgelist_flag==True: 2 22forfinalparseurl_firstinfinalparseurls_deep_first: #printfinalparseurl ifjudgeurl(finalparseurl_first): 3 2pass 22else: judgelist_flag=False 4 2break 5 returnjudgelist_flag 22#整体控制的run方法 defrun(): 6 2partlistskeys=partlists.keys() 2forlistpageurlinpartlistskeys: 7 22totalPageNo=parseListpageurl_rult['totalPageNo']#获取该list总共有多少页 #print'debug:totalPageNo',totalPageNo 8 2finallylistpageurl=listpageurl+'&page='+str(int(totalPageNo)+1)+'&J22L=6_0_0'#拼接出最后一个list页面(list页面有1、2、3。。。n页) #print'debug:finallylistpageurl',finallylistpageurl list已经爬取完list 9 3ifjudgelist(listpageurl,finallylistpageurl):#如果该0 毕了。那么,就跳过这个23print continue list,从其第1页,开始往下爬取 1 2else:#否则就逐个沿着2 233foriinrange(1,int(totalPageNo)+2): finalparseurl='' listpageurl_next=listpageurl+'&page='+str(i)+'&JL=6_0_0' #print\"debug:listpageurl_next\ 3 2parseListpageurl_rult=parseListpageurl(listpageurl_next) 32totalPageNo=parseListpageurl_rult['totalPageNo']#需要更行总的页面数量,以免数据陈旧 4 3finalparseurls_deep=list(parseListpageurl_rult['finalparseurls']) 5 23forfinalparseurlinfinalparseurls_deep: ifjudgeurl(finalparseurl):#判断该具体的url是否已经爬取 print'finalparseurlpassyet:'+finalparseurl 6 2pass 32else: finalurl_content=getfinalurl_content(partlists,listpageurl,finalpar7 3seurl) 8 finalparseurl_tmp=finalparseurl+\"\\n\" 23withopen(\"data.txt\将爬取完毕好的url写入data.txt datafile.writelines(finalurl_content+\"\\n\") url写入9 2withopen(\"judgeurl.txt\将已经爬取好的4judgeurl.txt 0 24judgefile.writelines(finalparseurl+\"\\n\") bisect.insort_right(judgeurl_all_lines,finalparseurl+\"\\n\") #主方法 1 2if__name__=='__main__': 42reload(sys) sys.setdefaultencoding('utf8')#设置系统默认编码是utf8 2 4socket.setdefaulttimeout(5)#设置全局超时时间 3 globaljudgeurl_all_lines#设置全局变量 24#不存在文件就创建文件,该文件用于记录哪些url是爬取过的,如果临时中断了,可以直接重启脚本即可 4 2ifnot 4withopen(\"judgeurl.txt\ 5 24judgefile.close() #每次运行只在开始的时候读取一次,新产生的数据(已怕去过的url)也会judgeurl.txt 保存到6 422withopen(\"judgeurl.txt\ judgeurl_all_lines=judgefile.readlines() judgeurl_all_lines.sort()#排序,因为后面需要使用到二分查找,必须先排7 4序 8 #启多个线程去爬取 24Thread(target=run(),args=()).start() Thread(target=run(),args=()).start() 9 2#Thread(target=run(),args=()).start() 50 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
因篇幅问题不能全部显示,请点此查看更多更全内容