环境:Windows7+python3.6+Pycharm2017html
目标:抓取链家北京地区已成交二手房信息(无需登陆),以下图,户型、朝向、成交时间价格等,保存到csv。最后一共抓取约58W数据,程序运行8h。python
---所有文章: 京东爬虫 、链家爬虫、美团爬虫、微信公众号爬虫、字体反爬、Django笔记、阿里云部署、vi\vim入门----json
1、打开北京二手房网页https://bj.lianjia.com/ershoufang/,默认显示的是在售二手房信息,一共45634套,可是只显示了100页,每页30条,这3000条信息是没有任何反爬的,能够直接抓取,若是要抓取所有45634条,应该要按小区来。本文主要讨论已成交二手房信息,数据量更大,难度也要高一点。vim
2、点击页面右上角成交,切换到已成交二手房信息,显示一共有73W条数据,可是也只显示100页,每页30条共3000条信息。并且还有个问题就是近30天内成交的房源的成交时间、价格信息是不显示的。咱们能够右键检查进入开发者模式,在网页的html代码中找到房源的详情页面的url,而后进入详情页面抓取成交时间、价格。服务器
3、如何抓取尽量多的房源信息微信
如今问题就是73W已成交二手房信息,怎么能尽量多的抓下来。 办法就是这些房源经过分类来抓取,好比分不一样区域,价格,小区,这样能够抓到更多的数据。本文选用按小区抓取。点击页面上方小区,进入以下页面,再点击返回所有小区列表。显示一共有11435个小区,虽然下面翻页只有30页,可是咱们能够经过构造url来翻页,实测能够翻到100页,100页后都是重复的,共3000个小区。每页的url以下:多线程
第2页:https://bj.lianjia.com/xiaoqu/pg2/app
第3页:https://bj.lianjia.com/xiaoqu/pg3/函数
第100页:https://bj.lianjia.com/xiaoqu/pg100/字体
4、如何抓取每一个小区的已成交二手房信息
点击某个小区如北京新天地,进入小区详情页面,下拉找到北京新天地小区成交记录,点击下面的查看所有成交记录,便可获得该小区所有已成交房源信息。经过左上角房源总数2133套,除以每页30套,咱们能够获得该小区已成交房源一共有多少页。近30天内成交的进入详情页面抓取。观察页面的url https://bj.lianjia.com/chengjiao/c1111027375945/ ,观察规律就是最后的一串数字是变化的,是每一个小区的id。翻页的规律以下:
第二页:https://bj.lianjia.com/chengjiao/pg2c1111027375945/
第三页:https://bj.lianjia.com/chengjiao/pg3c1111027375945/
因此咱们的思路就是先抓取每一个小区的id,而后构造小区成交房源页面的url,经过房源总数来得知该小区一共有多少页,翻页抓取。近30天内成交的须要进入详情页面抓取,其余的直接在列表页面就能够。
5、抓取小区id
一共100页,每页的url以下,也很简单,直接每一个li标签中的data-id属性就是小区的id。注意的是该页面有对ip访问次数作限制,单ip连续访问好像是25页就会被封,因此须要采用代理ip,这里每一个ip只抓取20页。还有一点须要注意的就是抓取的id须要作排重,此处用的set。还有就是对于第一次访问没有获得数据的页面须要再次访问。
https://bj.lianjia.com/xiaoqu/pg2/
https://bj.lianjia.com/xiaoqu/pg3/
实际一共抓取到2990个id,保存到本地csv,代码以下。
import requests from lxml import etree import csv import time import json #单线程抓取小区id前100页信息 def get_xiaoqu(x,p): head = {'Host': 'bj.lianjia.com', 'Referer': 'https://bj.lianjia.com/chengjiao/', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36' } n=x*20+1 l=list(range(n,n+20)) for i in l: url = 'https://bj.lianjia.com/xiaoqu/pg' + str(i) try: r = requests.get(url, headers=head, proxies=p, timeout=3) html = etree.HTML(r.text) datas=html.xpath('//li[@class="clear xiaoquListItem"]/@data-id') title=html.xpath('//li[@class="clear xiaoquListItem"]/div[@class="info"]/div[@class="title"]/a/text()') print('No:' + str(x), 'page:' + str(i), len(s), len(datas), len(title)) #若是当前页没有返回数据,将当前页数加到列表l末尾,再次抓取 if len(datas)==0: print(url) l.append(i) else: for data in datas: s.add(data) # 若是当前页访问出现异常,将当前页数加到列表l末尾,再次抓取 except Exception as e: l.append(i) print(e) print(' ****No:'+str(x)+' finish') #本人购买的代理获取方式,须要根据大家本身的修改。函数功能获取n个ip,并以列表形式返回,每一个元素为字典:{'https':'https://118.120.228.202:4286'} def get_ip(n): url='XXXXXXXXXXXXXXXXXXXXXXXX' r=requests.get(url) html=json.loads(r.text) proxies=[] for i in range(n): a=html['data'][i]['ip'] b=html['data'][i]['port'] val='https://'+str(a)+':'+str(b) p={'https':val} proxies.append(p) return(proxies) if __name__=='__main__': global s #将id保存在set中,达到排重效果 s = set() #该页面网站会禁ip,因此每一个ip只访问20页 for x in range(5): now=time.time() ls = get_ip(1) p=ls[0] get_xiaoqu(x,p) print(time.time()-now) print('******************') print('抓取完成') #将抓取的id保存到本地csv with open('xiaoqu_id.csv', 'a', newline='', encoding='gb18030')as f: write = csv.writer(f) for data in s: write.writerow([data]) f.close()
6、对每一个小区已成交房源信息进行抓取
本文没有开多线程(开5个线程跑了几分钟好像也没遇到问题),也没遇到封ip,就没加代理。逻辑很简单,parse_xiaoqu(url,pa) 函数用于爬取小区具体一页的房源信息,先抓取第一页数据,获取房源信息的同时得到该小区房源总数,而后肯定该小区一共有多少页。而后就是对每一页调用parse_xiaoqu(url,pa)进行抓取。主要注意点有:
一、对于第一次抓取失败的页面,包括timeout这种异常和无异常可是返回0条房源信息两种状况,都须要对这些页面进行第二次的抓取。parse_xiaoqu(url,pa)返回值中有一个1或0就是用以标记本次抓取是否成功。第一次抓取失败的,第二次抓取成功的数据还挺多的。
二、爬取过程当中遇到报错中断,能够经过已经抓取的小区数量,修改range(0,2990)函数的第一个参数达到断点后续抓。
代码以下,代码应该是把小区id导入就能够直接运行的。
import requests from lxml import etree import csv import time import threading #小区具体一页房源信息的抓取,输入为当前页面url,当前爬取页数pa。返回数据为小区房源总数num,该页抓取的房源信息home_list,状态码1或0(1表示成功) def parse_xiaoqu(url,pa): head = {'Host': 'bj.lianjia.com', 'Referer': 'https://bj.lianjia.com/chengjiao/', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36' } r = requests.get(url, headers=head,timeout=5) html = etree.HTML(r.text) num = html.xpath('//div[@class="content"]//div[@class="total fl"]/span/text()')[0] num = int(num) datas = html.xpath('//li/div[@class="info"]') print('小区房源总数:', num,'第%d页房源数:'%pa,len(datas)) print(url) if len(datas)==0: return(num,[],0) #服务器无返回数据,状态码返回0 house_list=[] for html1 in datas: title = html1.xpath('div[@class="title"]/a/text()') info = html1.xpath('div[@class="address"]/div[@class="houseInfo"]/text()') floor = html1.xpath('div[@class="flood"]/div[@class="positionInfo"]/text()') info[0] = info[0].replace('\xa0','') #该条信息中有个html语言的空格符号 ;须要去掉,否则gbk编码会报错,gb18030显示问号 date = html1.xpath('div[@class="address"]/div[@class="dealDate"]/text()') #30天内成交的进入详情页面抓取 if date[0] == '近30天内成交': p_url = html1.xpath('div[@class="title"]/a/@href') r = requests.get(p_url[0], headers=head,timeout=5) html = etree.HTML(r.text) price = html.xpath('//div[@class="overview"]/div[@class="info fr"]/div[@class="price"]/span/i/text()') unitprice = html.xpath('//div[@class="overview"]/div[@class="info fr"]/div[@class="price"]/b/text()') date = html.xpath('//div[@class="house-title LOGVIEWDATA LOGVIEW"]/div[@class="wrapper"]/span/text()') #有的房源信息没有价格信息,显示暂无价格 if len(price)==0: price.append('暂无价格') if len(unitprice)==0: unitprice.append('暂无单价') date[0] = date[0].replace('链家成交', '') a = [title[0], info[0], floor[0], date[0], price[0], unitprice[0]] house_list.append(a) print(title[0], info[0], floor[0], date[0], price[0], unitprice[0]) else: price = html1.xpath('div[@class="address"]/div[@class="totalPrice"]/span/text()') unitprice = html1.xpath('div[@class="flood"]/div[@class="unitPrice"]/span/text()') if len(price) == 0: price = ['暂无价格'] if len(unitprice) == 0: unitprice = ['暂无单价'] a = [title[0], info[0], floor[0], date[0], price[0], unitprice[0]] house_list.append(a) print(title[0], info[0], floor[0], date[0], price[0], unitprice[0]) print(' ********************* ','第%d页完成!'%pa) return (num,house_list,1) #抓取某小区全部已成交二手房信息,排重后存入本地csv,输入为小区id,返回抓取到的该小区的房源总数 def crow_xiaoqu(id): url='https://bj.lianjia.com/chengjiao/c%d/'%int(id) h_list=[] #保存该小区抓取的全部房源信息 fail_list=[] #保存第一次抓取失败的页数,第一遍抓取完成后对这些页数再次抓取 try: #爬取小区第一页信息 result=parse_xiaoqu(url,1) except: #若是第一页信息第一次爬取失败,sleep2秒再次爬取 time.sleep(2) result=parse_xiaoqu(url,1) #获取该小区房源总数num num = result[0] #若是无数据返回,sleep2秒再爬取一次 if num == 0: time.sleep(2) result=parse_xiaoqu(url,1) num = result[0] new_list = result[1] pages=1 for data in new_list: if data not in h_list: h_list.append(data) # 肯定当前小区房源页数pages if num > 30: if num % 30 == 0: pages = num // 30 else: pages = num // 30 + 1 for pa in range(2,pages+1): new_url = 'https://bj.lianjia.com/chengjiao/pg'+str(pa)+'c'+str(id) try: result=parse_xiaoqu(new_url,pa) status=result[2] if status==1: new_list=result[1] #排重后存入h_list for data in new_list: if data not in h_list: h_list.append(data) else: fail_list.append(pa) except Exception as e: fail_list.append(pa) print(e) print(' 开始抓取第一次失败页面') for pa in fail_list: new_url = 'https://bj.lianjia.com/chengjiao/pg' + str(pa) + 'c' + str(id) print(new_url) try: result = parse_xiaoqu(new_url,pa) status = result[2] if status == 1: new_list = result[1] for data in new_list: if data not in h_list: h_list.append(data) else: pass except Exception as e: print(e) print(' 抓取完成,开始保存数据') #一个小区的数据所有抓完后存入csv with open('lianjia_123.csv','a',newline='',encoding='gb18030')as f: write=csv.writer(f) for data in h_list: write.writerow(data) #返回抓取到的该小区房源总数 return(len(h_list)) if __name__=='__main__': counts=0 #记录爬取到的房源总数 now=time.time() id_list=[] with open('xiaoqu_id.csv','r')as f: read=csv.reader(f) for id in read: id_list.append(id[0]) m=0 #能够经过修改range函数的起始参数来达到断点续抓 for x in range(0,2990): m+=1 #记录一共抓取了多少个小区 print(' 开始抓取第'+str(m)+'个小区') time.sleep(1) count=crow_xiaoqu(id_list[x]) counts=counts+count #打印已经抓取的小区数量,房源数量,所花的时间 print(' 已经抓取'+str(m)+'个小区 '+str(counts)+'条房源信息',time.time()-now)
7、总结
一共抓取了 58W条数据,程序跑了8h,速度通常。主要缺点就是程序不够顽强,中途中断了不少次,须要人工的再次启动,修改range参数才能断点续抓。还有就是1.1W个小区也就抓了3000个,可是从房源数据来看73W数据抓了58w,大概80%。
水平有限,但愿你们指正。
---所有文章: 京东爬虫 、链家爬虫、美团爬虫、微信公众号爬虫、字体反爬、Django笔记、阿里云部署、vi\vim入门----
欢迎关注我的公众号,更多案例持续更新!