赞
踩
最近帮同学爬取了链家在售二手房的信息,这是本人学习爬虫以来爬取数据最多的一次,就想着写个博客记录一下。
本次爬虫是利用python+jupyter notebook实现的,用到的第三方库如下:
import pandas as pd # 数据存储
import requests # 网页内容获取
import re # 解析数据
from lxml import etree # 解析数据
import random
import time # 反反爬
from fastprogress import master_bar,progress_bar # 进度条显示
本次爬取链家网站中温州二手房的信息https://wz.lianjia.com/ershoufang/:
通过F12(或单击右键选择检查)打开浏览器自带的抓包工具,然后根据下图中的顺序点击鼠标,然后就得到了网页源码中含有的第一套房产的信息:
通过对上图进行分析,可以通过正则表达式来获取到每个二手房详情页的url,先来测试一下,看一看获取到的源码和上面看到的是否一致:
def ua(): """随机获取一个浏览器用户信息""" user_agents = [ 'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11', 'Opera/9.25 (Windows NT 5.1; U; en)', 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)', 'Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Kubuntu)', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12', 'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9', 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Ubuntu/11.04 Chromium/16.0.912.77 Chrome/16.0.912.77 Safari/535.7', 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0', ] agent = random.choice(user_agents) return { 'User-Agent': agent } def get(url): """ 获取网页源码 url: 目标网页的地址 return:网页源码 """ res = requests.get(url=url, headers = ua()) return res.text def get_url(res_text): """ 获取源码中每个二手房详情页的url res_text:网页源码 return:列表形式的30个二手房详情页的url """ re_f = '<a class="" href="(.*?)" target="_blank"' url_list = re.findall(re_f, res_text) return url_list
如下图所示,成功得到了第一页中每套二手房的详情页的url。然后我们再对二手房的详情页进行分析
通过2.1的操作现在成功拿到了详情页的url,打开详情页的链接后,接下来先用于上一小节相同的方式对网页进行分析:
我们通过对网页的分析发现,详情页的房屋信息任然是保存再网页源码中的,现在我们可以尝试着获取房屋的标题:
def get_else_data(res_text):
res_text = etree.HTML(res_text)
title = res_text.xpath("//div[@class='sellDetailHeader']//h1/@title")
return dict(zip(['标题'], [title]))
ok,成功了,这说明我们可以通过同样的方式获取房屋的其它信息。
根据我同学的要求,通过Xpath对网页源码就行解析,这样就能得到想要的数据了:
def get_data(res_text): """获取房屋的详细数据""" res_text = etree.HTML(res_text) # 获取房屋的标题 title = res_text.xpath("//div[@class='sellDetailHeader']//h1/@title") # 获取房屋的总价 total_price = res_text.xpath("//div[@class='overview']//div/span/text()")[2] # 获取房屋的单价 price = res_text.xpath("//div[@class='overview']//div/span/text()")[3] # 获取房屋的地段 place = res_text.xpath("//div[@class='overview']//div/span/a/text()") ## 房屋基本信息获取 # 获取房屋基本信息的标题 lab = res_text.xpath("//div[@class='base']//span/text()") # 获取房屋基本信息的内容 val = res_text.xpath("//div[@class='base']//li/text()") ## 获取房源交易信息 # 获取房源交易标题 key1 = res_text.xpath("//div[@class='transaction']//span[1]//text()") # 获取房源交易信息内容 trans = res_text.xpath("//div[@class='transaction']//span[2]//text()") ## 获取房源特色信息 # 获取房源特色标题 key = res_text.xpath("//div[@class='baseattribute clear']/div[@class='name']/text()") # 获取房源特色内容 val1 = res_text.xpath("//div[@class='baseattribute clear']/div[@class='content']/text()") # 返回包含上述信息的字典 return dict(zip(['标题', '总价格', '单价', '地段'] + lab + key1 + key, [title, total_price, price, place] + val + trans + val1))
通过上面的代码就能得到想要的房屋的全部的基本信息。到此就实现了对某一页的房屋信息的爬取,并且通过对房屋详情页url列表的遍历,也能实现对某一页中全部30个房屋信息的爬取,接下来的问题就是如何实现爬取不同页码的内容?
其实再爬虫过程中实现翻页操作非常的简单,只需要对比不同页码的网页之间的url的不同,找到其中的规律,根据找到的规律,就能完美实现翻页的操作了。
下面对比链家二手房不同页码网页之间的url差别:
通过上面的对比发现,链家是通过在首页的url(https://qd.lianjia.com/ershoufang/)之后加上pg页码/来实现的翻页操作,对此我们可以遍历我们想要爬取的页码,再对页码就行遍历形成对应的url,这样就能实现对不同页码的爬取。
在分析页码的过程中我们发现链家只显示100页的数据也就是3000个房屋的基本信息,这样的数据量太少了,没达到我同学的要求,那我们该咋办呢?
通过简单的分析,我们想到了通过按区来爬取数据。选取页码为100的区,这样就能得到房屋基本信息,通过简单的分析网页的url:
链家网站分区分页的url是由首页的url(https://qd.lianjia.com/ershoufang/)+各区的拼音/+pg页码/构成的,通过便利各区和页码列表我们就能得到我们想要的网页的url。
找到了想要爬取网页的url接下来就可以进行爬虫操作了,但是由于本次爬取的数据较多,代码运行时间较长,我们的爬虫很可能在运行过程中会被检测出来,在爬取数据的时候链家回对爬虫发起的请求进行人机验证,这样爬虫就不能正常运行。为了解决这个问题我们采取了以下的措施:
def main(qu,start_pg=1, end_pg=100, download_times=1): """爬虫程序 qu: 传入要爬取的qu的拼音的列表 start_pg:开始的页码 end_pg:结束的页码 download_times:第几次下载 """ for q in qu: # 获取当前区的首页url url = 'https://qd.lianjia.com/ershoufang/' + q + '/' # 数据储存的列表 data = [] # 文件保存路径 filename = '二手房-' + q +'第' + str(download_times) + '次下载.csv' print('二手房-' + q +'第' + str(download_times) + '次下载') mb=master_bar(range(start_pg, end_pg+1)) for i in mb: # 获取每页的url new_url = url + 'pg' + str(i) + '/' # 获取当前页面包含的30个房屋详情页的url url_list = get_url(get(new_url)) for l in progress_bar(range(len(url_list)),parent=mb): # 反爬随机停止一段时间 a = random.randint(2, 5) if l % a == 0: time.sleep(2 * random.random()) # 获取当前页面的源码 text = get(url_list[l]) # 获取当前页面的房屋信息 data.append(get_data(text)) # 反爬随机停止一段时间 time.sleep(3 * random.random()) mb.child.comment = '正在爬取第'+str(l + 1) +'条数据!!' mb.main_bar.comment = '正在爬取第'+str(i+1) +'页数据!!' # 反爬随机停止一段时间 time.sleep(5 * random.random()) if i % 5 == 0: # 每5页保存一次数据 pd.DataFrame(data).to_csv(filename, encoding='GB18030') mb.write('前'+str(i)+'页数据已保存')
代码运行效果如下: 如果中途代码意外终止,我们向main()中传入相应的参数来继续下载,最后我们再把下载的数据合并在一个这样这次的爬虫工作就圆满结束了。
本次爬虫的难度比较低,出了烦人的人机验证以外,在爬虫过程中就没有碰到其它的反爬措施,通过这次爬虫我最大的收获就是发现了自己能力的不足:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。