1、python 爬虫实战 ,多线程爬取京东 jd html 页面:无需登录的网站的爬虫实战 2014-12-02 20:04:31标签:网站 爬虫 python import 版权声明:原创作品,如需转载,请与作者联系。否则将追究法律责任。 【前言】123456# 本脚本用来爬取 jd的页面:http:/ http:/ tag in self.handledtags and len(attrs)=1 :self.flag=self.data=self.processing=tagfor href,url in attrs:#非常关键的是,找出你想的 url和不606162636465666768
2、69707172737475767778798081想要的 url的区别#print debug:attrs,attrsif pattern3.match(url):#print debug:url,urlself.lasturl=urlelse:passdef handle_data(self, data):if self.processing:#去掉空格pass#其实这里我们根本没使用获取到得 data,就 pass把else:passdef handle_endtag(self, tag):if tag=self.processing:self.processing=Nonedef ge
3、tlinks(self):return self.setlinksdef getlasturl(self):return self.lasturl#定义一个 FinallPageParser,用于解析最终的 html页面,如http:/ parser,关键是怎样分析页面,最终写出代码,并且验证,这里就不详细说了class FinallPageParser(HTMLParser):def _init_(self):self.handledtags=div,h1,strong,a,del,div,img,li,span,tbody,tr,th,td,iself.processing=Nonesel
4、f.title=self.jdprice=self.refprice=self.partimgs_show=set()#展示图片self.partimgs=set()#详情图片82838485868788899091929394959697989910010110self.partdetail=#商品详情,参数等self.specification=#规格参数self.typeOrsize=set()#尺码和类型self.div=self.flag=self.flagrefprice=self.flagtitle=self.flagjdprice=self.flagtypeOrsize=sel
5、f.flagpartimgs=self.flagpartdetail=self.flagspecification=self.flagtypeOrsize=self.link=self.partslinks=HTMLParser._init_(self)def handle_starttag(self, tag, attrs):self.titleflag=self.flagrefprice=self.flagtitle=self.flagjdprice=self.flagtypeOrsize=self.flagpartimgs=self.flagpartdetail=self.flagspe
6、cification=self.flagtypeOrsize=if tag in self.handledtags:self.data=self.processing=tagif tag=div:for key,value in attrs:self.div=value# 取出 div的 name,判断是否是所需要的图片等元素if tag=i:self.flagtypeOrsize=match21031041051061071081091101111121131141151161if tag=a and len(attrs)=2:tmpflag=“for key,value in attrs:
7、if key=href and re.search(rhttp:/ key=title and value!=“:tmpflag=tmpflag+“second“if tmpflag= “firstsecond“:self.flagtypeOrsize=matchif tag=h1:self.flagtitle=matchif tag=strong and len(attrs)=2:for tmpclass,id in attrs:if id=jd-price:self.flagjdprice=matchif tag=del:self.flagrefprice=matchif tag=li:s
8、elf.flagpartdetail=matchif tag=th or tag=tr or tag=td :#+#879498.html td中有 br的只取到第一个,需要把喜欢为“”self.flagspecification=matchif tag=img :imgtmp_flag=imgtmp=for key,value in attrs:if re.search(rhttp:/img.*jpg|http:/img.*gif|http:/img.*png,str(value) and (key=src or key=data-lazyload):imgtmp=valueif key=
9、width:#可能还有 logoif re.search(rd1,9$,value):if int(value)就表示 tag结尾,所以必须替换,遇到替换为 BRBR,否则会解析失败htmlcontent=pile().sub(BRBR,htmlcontent)parser.feed(htmlcontent)finalparseurl=parser.getdata()if finalparseurl:print datetime.datetime.now().strftime(“%Y-%m-%d %H:%M:%S“)+“,parse finalparseurl succ:“+finallyur
10、lelse:print datetime.datetime.now().strftime(“%Y-%m-%d %H:%M:%S“)+“,parse finalparseurl fail:“+finallyurlreturn finalparseurl#获取图片的方法def getimg(imgdir,imgurl):imgobj=urlparse(imgurl)getimgurl=imgobj.pathimgtmppathlist=getimgurl.split(/)imgname=imgtmppathlistlen(imgtmppathlist)-1if not os.path.exists
11、(imgdir):try:os.makedirs(imgdir)except Exception,e:print esavefile=imgdir+“/“+imgnameif not os.path.exists(savefile):sendhttp_rult=sendhttp(getimgurl,imgobj.hostname,savefile)if sendhttp_rult:print datetime.datetime.now().strftime(“%Y-%m-%d %H:%M:%S“)+“,sent http request succ,getimg:“+imgurlelse:pri
12、nt datetime.datetime.now().strftime(“%Y-%m-05206207208209210211212213214215216217218219%d %H:%M:%S“)+“,sent http request fail,getimg:“+imgurlelse:pass#获取价格def getprice(pricedir,priceurl):priceobj=urlparse(priceurl)getpriceurl=priceobj.path+“?“+priceobj.querypricename=“price“if not os.path.exists(pri
13、cedir):try:os.makedirs(pricedir)except Exception,e:print esavefile=pricedir+“/“+pricenameif not os.path.exists(savefile):sendhttp_rult=sendhttp(getpriceurl,priceobj.hostname,savefile)if sendhttp_rult:print datetime.datetime.now().strftime(“%Y-%m-%d %H:%M:%S“)+“,sent http request succ,getprice:“+pric
14、eurlelse:print datetime.datetime.now().strftime(“%Y-%m-%d %H:%M:%S“)+“,sent http request fail,getprice:“+priceurlelse:passwith open(savefile) as file:price_content=file.read()price_content=pile(cnp(|);).sub(,price_content)price_dic=“id“:“0“,“p“:“0“,“m“:“0“if re.search(r:,price_content):try:price_dic
15、=json.loads(price_content)#以免数据格式不对悲剧except Exception,e:22022122222322422522622722822923023123223323print ereturn “jdprice“:price_dicp,refprice:price_dicm#获取最后页面的具体内容def getfinalurl_content(partlists,listpageurl,finalparseurl):parseFinallyurl_rult=parseFinallyurl(finalparseurl)htmlname_tmp=urlparse(
16、finalparseurl).pathimgtopdir_tmp=“img/“+htmlname_tmp.split(/)10:2imgdir=imgtopdir_tmp+htmlname_tmp+“/introduction“imgshowdir=imgtopdir_tmp+htmlname_tmp+“/show“partdetail_tmp=“for imgurl in parseFinallyurl_rultpartimgs:#获取商品介绍的图片getimg(imgdir,imgurl)for imgshowurl in parseFinallyurl_rultpartimgs_show
17、:#获取展示图片getimg(imgshowdir,imgshowurl)for key in parseFinallyurl_rultpartdetail.keys():partdetail_tmp=partdetail_tmp+key+“$“+parseFinallyurl_rultpartdetailkey+“,“#商品介绍specification_tmp=“i=0for specification_var in parseFinallyurl_rult“specification“:#规格参数if i=0:str_slip=“elif(i%2=0 and i!=0 ):str_sli
18、p=“,“else:str_slip=“$“specification_tmp=specification_tmp+str_slip+specification_vari=i+1typeOrsize_tmp=“for typeOrsize_var in parseFinallyurl_rulttypeOrsize:42352362372382392402412422432442452462472482typeOrsize_tmp=typeOrsize_tmp+“,“+typeOrsize_varpriceurl=“http:/ url#parseFinallyurl_rult“title“):
19、标题#parseFinallyurl_rult“jdprice“:京东的价格#parseFinallyurl_rult“refprice“:市场参考价格#imgshowdir:商品展示的图片保存位置#imgdir:商品说明的图片保存位置:jd 的商品说明也是用图片的#partdetail_tmp:商品的详细信息#specification_tmp:商品的规则参数#typeOrsize_tmp:商品的类型和尺寸return str(partlistslistpageurl).strip()+“t“+finalparseurl.strip()+“t“+str(parseFinallyurl_rul
20、t“title“).strip()+“t“+str(parseFinallyurl_rult“jdprice“).strip()+“t“+str(parseFinallyurl_rult“refprice“).strip()+“t“+imgshowdir.strip()+“t“+imgdir.strip()+“t“+partdetail_tmp.strip()+“t“+specification_tmp.strip()+“t“+typeOrsize_tmp.strip()#判断最后的页面(商品详情页)是否被爬取了def judgeurl(url):#优化后,使用二分法查找 url(查找快了,同
21、时也不用反复读取文件了)。第一次加载 judgeurl_all_lines之后,维护好此 list,同时新增的 url也保存到 judgeurl.txt中url=url+“n“global judgeurl_all_linesfind_url_flag=Falseurl_point=bisect.bisect(judgeurl_all_lines,url)#这里使用二分法49250251252253254255256257258259260261262263快速查找(前提:list 是排序好的)find_url_flag = judgeurl_all_lines and judgeurl_al
22、l_linesurl_point-1 = urlreturn find_url_flag#判断 list页面是否已经爬取完毕了#这里的逻辑是:第一个 list中的所有 url、最后 list的所有 url都爬取完毕了,那么久说明 list的所有 page爬取完毕了(实际上是一种弱校验)。#调用了 judgeurl得方法def judgelist(listpageurl,finallylistpageurl):#判断第一个、最后一个的 list页面的所有的 html是否下载完毕,以此判断该类型是否处理完毕judgelist_flag=TrueparseListpageurl_rult_final
23、ly=parseListpageurl(finallylistpageurl)finalparseurls_deep_finally=list(parseListpageurl_rult_finallyfinalparseurls)#获取到最后的需要解析的 url的列表parseListpageurl_rult_first=parseListpageurl(listpageurl)finalparseurls_deep_first=list(parseListpageurl_rult_firstfinalparseurls)#获取到最后的需要解析的 url的列表for finalparseur
24、l in finalparseurls_deep_finally:#print finalparseurlif judgeurl(finalparseurl):passelse:judgelist_flag=Falsebreakif judgelist_flag=True:for finalparseurl_first in finalparseurls_deep_first:#print finalparseurlif judgeurl(finalparseurl_first):passelse:26426526626726826927027127227327427527627727judg
25、elist_flag=Falsebreakreturn judgelist_flag#整体控制的 run方法def run():partlists=http:/ listpageurl in partlistskeys:parseListpageurl_rult=parseListpageurl(listpageurl)#开始解析 list页面,如:http:/ debug:totalPageNo,totalPageNofinallylistpageurl=listpageurl+&page=+str(int(totalPageNo)+1)+&JL=6_0_0#拼接出最后一个 list页面(l
26、ist 页面有1、2、3。n 页)#print debug:finallylistpageurl ,finallylistpageurlif judgelist(listpageurl,finallylistpageurl):#如果该 list已经爬取完毕了。那么,就跳过这个 listprint datetime.datetime.now().strftime(“%Y-%m-%d %H:%M:%S“)+,All html done for +str(listpageurl)+“:“+str(partlistslistpageurl)+“【Done Done】,【_】“continueelse:
27、#否则就逐个沿着 list,从其第 1页,开始往下爬取for i in range(1,int(totalPageNo)+2):finalparseurl=listpageurl_next=listpageurl+&page=+str(i)+&JL=6_0_0#print “debug:listpageurl_next“,listpageurl_nextparseListpageurl_rult=parseListpageurl(listpageurl_next)totalPageNo=parseListpageurl_rulttotalPageNo#需要更行总的页面数量,以免数据陈旧fina
28、lparseurls_deep=list(parseListpageurl_rultfin82792802812822832842852862872882892902912922alparseurls)for finalparseurl in finalparseurls_deep:if judgeurl(finalparseurl):#判断该具体的 url是否已经爬取print finalparseurl pass yet:+finalparseurlpasselse:finalurl_content=getfinalurl_content(partlists,listpageurl,fin
29、alparseurl)finalparseurl_tmp=finalparseurl+“n“with open(“data.txt“,“a“) as datafile:#将爬取完毕好的 url写入 data.txtdatafile.writelines(finalurl_content+“n“)with open(“judgeurl.txt“,“a“) as judgefile:#将已经爬取好的 url写入 judgeurl.txtjudgefile.writelines(finalparseurl+“n“)bisect.insort_right(judgeurl_all_lines,fina
30、lparseurl+“n“)#主方法if _name_ = _main_:reload(sys)sys.setdefaultencoding(utf8)#设置系统默认编码是 utf8socket.setdefaulttimeout(5)#设置全局超时时间global judgeurl_all_lines#设置全局变量#不存在文件就创建文件,该文件用于记录哪些 url是爬取过的,如果临时中断了,可以直接重启脚本即可if not os.path.exists(“judgeurl.txt“):with open(“judgeurl.txt“,w) as judgefile:judgefile.clo
31、se()#每次运行只在开始的时候读取一次,新产生的数据(已怕去过的 url)也会保存到 judgeurl.txtwith open(“judgeurl.txt“,“r“) as judgefile:93294295296297298299300301302303304305306307judgeurl_all_lines=judgefile.readlines()judgeurl_all_lines.sort()#排序,因为后面需要使用到二分查找,必须先排序#启多个线程去爬取Thread(target=run(),args=().start()Thread(target=run(),args=().start()#Thread(target=run(),args=().start()3083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803