本次是爬取西安房天下上的二手房交易数据,主要面临的困难有:
import requests
from scrapy.selector import Selector as se
import pandas as pd
import re
headers = {
'Connection': 'keep-alive',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36 Edg/103.0.1264.37',
}
datas = []
fail_url = []
for page in range(73, 76):
# https://xian.esf.fang.com/house/i32/
response = requests.get('https://xian.esf.fang.com/house/i3'+str(page+25)+'/', headers=headers,
verify=True)
html = response.text
next_page_url = 'https://xian.esf.fang.com/house/i3' + \
str(page+1)+'?'+re.findall(r"t3='(.*?)'", html)[0]
response = requests.get(next_page_url, headers=headers,
verify=True)
next_url = se(text=response.text).xpath(
'//h4[@class="clearfix"]/a/@href').extract()
for url in next_url:
response1 = requests.get('https://xian.esf.fang.com'+url, headers=headers,
verify=True)
html = response1.text
page_url = 'https://xian.esf.fang.com' + \
url+'?'+re.findall(r"t3='(.*?)'", html)[0]
print(f"开始爬取{page_url}")
response = requests.get(page_url, headers=headers,
verify=True)
data = response.text
# print(data)
# //*[@id="lpname"]/h1/span
# title
x1 = se(text=data).xpath(
'//span[@class="tit_text"]/text()').extract_first().replace('\n', '').strip()
# 总价
x2 = se(text=data).xpath(
'//div[@class="trl-item price_esf sty1"]/i/text()').extract_first()
# 户型
x3 = se(text=data).xpath(
'//div[text()="户型"]/../div/text()').extract_first()
# '//div[text()="户型"]/../div/text()').extract_first().replace('\n', '').strip()
# 建筑面积
x4 = se(text=data).xpath(
'//div[@class="trl-item1 w182"]/div/text()').extract_first()
# 单价
x5 = se(text=data).xpath(
'//div[@class="trl-item1 w132"]/div/text()').extract_first()
# 朝向
x6 = se(text=data).xpath(
'//div[text()="朝向"]/../div[@class="tt"]/text()').extract_first() # .replace('\n', '').strip()
# 楼层
x7 = se(text=data).xpath(
'//div[text()="朝向"]/../../div[@class="trl-item1 w182"]/div[@class="tt"]/a/text()').extract_first()
# 总楼层
x27 = se(text=data).xpath(
'//div[text()="朝向"]/../../div[@class="trl-item1 w182"]/div[@class="font14"]/text()').extract_first()
# 装修
x8 = se(text=data).xpath(
'//div[text()="装修"]/../div/a/text()').extract_first()
# 小区
x9 = se(text=data).xpath(
'//div[@class="rcont"]/a/text()').extract_first()
# 小区位置具体描述
x10 = se(text=data).xpath(
'//div[@class="rcont"]/span/text()').extract_first()
# 区
x11 = se(text=data).xpath(
'//div[@id="address"]/a/text()').extract()[0].replace('\n', '').strip()
# 区域
x12 = se(text=data).xpath(
'//div[@id="address"]/a/text()').extract()[1].replace('\n', '').strip()
# 建筑年代
x13 = se(text=data).xpath(
'//span[text()="建筑年代"]/../span[@class="rcont "]/text()').extract_first()
# 有无电梯
x14 = se(text=data).xpath(
'//span[text()="有无电梯"]/../span[@class="rcont"]/text()').extract_first()
# 产权性质
x15 = se(text=data).xpath(
'//span[text()="产权性质"]/../span[@class="rcont"]/a/text()').extract_first()
# 住宅类别
x16 = se(text=data).xpath(
'//span[text()="住宅类别"]/../span[@class="rcont"]/a/text()').extract_first()
# 建筑结构
x17 = se(text=data).xpath(
'//span[text()="建筑结构"]/../span[@class="rcont"]/a/text()').extract_first()
# 建筑类别
x18 = se(text=data).xpath(
'//span[text()="建筑类别"]/../span[@class="rcont"]/a/text()').extract_first()
# 挂牌时间
if se(text=data).xpath(
'//span[text()="挂牌时间"]/../span[@class="rcont"]/text()').extract_first():
x19 = se(text=data).xpath(
'//span[text()="挂牌时间"]/../span[@class="rcont"]/text()').extract_first().replace('\n', '').strip()
else:
x19 = se(text=data).xpath(
'//span[text()="挂牌时间"]/../span[@class="rcont"]/text()').extract_first()
# 参考均价
x20 = se(text=data).xpath(
'//span[text()="参考均价"]/../span/i/text()').extract_first()
# 物业类型
x21 = se(text=data).xpath(
'//span[text()="物业类型"]/../span[@class="rcont "]/text()').extract_first()
# 绿化率
x22 = se(text=data).xpath(
'//span[text()="绿 化 率"]/../span[@class="rcont "]/text()').extract_first()
# 容积率
x23 = se(text=data).xpath(
'//span[text()="容 积 率"]/../span[@class="rcont "]/text()').extract_first()
# 建筑类型
x24 = se(text=data).xpath(
'//span[text()="建筑类型"]/../span[@class="rcont "]/text()').extract_first()
# 人车分流
x25 = se(text=data).xpath(
'//span[text()="人车分流"]/../span[@class="rcont "]/text()').extract_first()
# 总户数
x26 = se(text=data).xpath(
'//span[text()="总 户 数"]/../span[@class="rcont "]/text()').extract_first()
x28 = se(text=data).xpath(
'//span[text()="总楼栋数"]/../span[@class="rcont "]/text()').extract_first()
# 总评分
x29 = se(text=data).xpath(
'//p[text()="房源评级|多维度分析房源"]/../div[@class="num_bg"]/h3/text()').extract_first()
x30 = se(text=data).xpath(
'//p[text()="交易价值"]/../h4/text()').extract_first()
x31 = se(text=data).xpath(
'//p[text()="居住品质"]/../h4/text()').extract_first()
x32 = se(text=data).xpath(
'//p[text()="便利指数"]/../h4/text()').extract_first()
x33 = se(text=data).xpath(
'//p[text()="物业服务"]/../h4/text()').extract_first()
datas.append([x1, x2, x3, x4, x5, x6, x7, x27, x8, x9, x10, x11, x12, x13,
x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x28, x30, x31, x32, x33, x29])
print(f'第{page+1}页数据爬取完成')
final_data = pd.DataFrame(datas, columns=['title', '总价', '户型',
'建筑面积', '单价', '朝向',
'楼层', '总楼层', '装修',
'小区', '小区位置具体描述',
'所属区', '所属具体区域', '建筑年代',
'有无电梯', '产权性质', '住宅类别',
'建筑结构', '建筑类别', '挂牌时间',
'小区参考均价', '物业类型', '绿化率',
'容积率', '小区建筑类型', '人车分流',
'总户数', '总楼栋数', '交易价值',
'居住品质', '便利指数', '物业服务',
'综合评分'])
# final_data=final_data.set_index('id')
final_data.to_excel('数据.xlsx')上述代码是本次校赛仓促之间写出的代码,还有一些地方需要优化:
本次爬虫总的来看是一次不怎么成功的爬虫项目经历,首先时间限制加上对于Scrapy框架不够熟悉导致在实际爬取数据过程中并没有使用Scrapy框架,临时使用的requests库也存在一些需要优化的地方:
在爬取房天下的网页过程中遇到的一个问题是输入网页链接后拿到的html文件中并不是实际我们想要拿到的html文件,主要原因是因为实际访问的页面中还存在一串自动编码:

即面临的网页重定向问题,这个问题的解决主要是依据想办法通过初次访问拿到的html文件中做匹配得到真实访问的链接,观察获取到的html文件可以发现,实际的链接在返回html的两个位置:
<div class="redirect" style="padding-top: 40px;">
<p class="info" style="font-size: 18pt; margin-bottom: 8px;">自动跳转中<span class="second"></span>s...</p>
<a class="btn-redir" style="font-size: 14pt;" href="https://xian.esf.fang.com/house/i31/?rfss=1-4906b68e40073d2ab0-41">点击跳转</a>var t4='https://xian.esf.fang.com/house/i31/';
var t3='rfss=1-4906b68e40073d2ab0-41';
location.href=t4+'?'+t3;这些都可以从html文件中直接获取得到,然后这个链接其实才是我们访问所需要的真正链接,第一种情况应该是更常见的一种重定向情况。
下次遇到网页的重定向可以先用正则匹配一下页面的链接来寻找真正可以拿到数据的url,另外就是需要注意的是这里拿到的url并非链接地址栏显示的url,据我观察链接地址栏的url会在一段时间后失效,具体的机理分析仍有待进一步的深入学习
经过一番重塑修改,对代码的逻辑进行进一步优化,最后写出了最终版本的房天下二手房交易数据爬取py代码:
import requests
import re
from scrapy.selector import Selector as se
import time
#import _thread
# 爬虫结果输出路径
out_dir = '爬虫结果.csv'
#列名
columns = ['title', '总价', '户型', '建筑面积',
'朝向', '楼层', '总楼层', '装修',
'所在小区', '区', '区域', '建筑年代',
'有无电梯', '挂牌时间', '小区参考均价',
'物业类型', '绿化率', '容积率', '建筑类型',
'人车分流', '总户数', '总楼栋数', '交易价值',
'居住品质', '便利指数', '物业服务', '总评分',
'单价']
headers = {
'Connection': 'keep-alive',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36 Edg/103.0.1264.37',
#'Cookie': 'csrfToken=fxB64yKN6YmKp2x6IBImOond; global_cookie=4qsim60u3xw9srizbptt3nh3q1yl5377g9v; engine_source_cookie=bing; sf_source=bing; city=xian; lastscanpage=0; g_sourcepage=esf_fy%5Exq_pc; unique_cookie=U_4qsim60u3xw9srizbptt3nh3q1yl5377g9v*62',
}
# 记录匹配失败网页
fail_url=[]
def get_url(url):
"""
获取html文件
"""
response = requests.get(url, headers=headers, verify=True)
print(f"开始爬取{url}")
time.sleep(0.1)
return response
def process_url(response):
"""
返回重定向后的url(解决网页的重定向问题)
"""
html = response.text
""" try:
url_head = re.findall(r"t4='(.*?)'", html)[0]
url_tail = re.findall(r"t3='(.*?)'", html)[0]
except:
print(f'{response.url}解析失败') """
return se(text=html).xpath('//div[@class="redirect"]/a[1]/@href').extract_first() # url_head+'?'+url_tail
def parse_page(response):
"""
返回当前房源页面的所有房子的链接以及下一个房源页面的url
"""
# 解析页面提取页面内的房源url
html = response.text
house_url_list = se(text=html).xpath(
'//h4[@class="clearfix"]/a/@href').extract()
temp_url = se(text=html).xpath(
'//a[text()="下一页"]/@href').extract_first()
if temp_url:
return house_url_list, 'https://xian.esf.fang.com'+temp_url
#elif temp_url[-3:-1]%25==0:
# return house_url_list,None
else:
return house_url_list,temp_url
def save(data, out_dir,column_name):
"""
保存爬取数据
"""
import csv
import os
if not os.path.exists(out_dir):
with open(out_dir, 'w', newline='', encoding='utf-8') as fp:
csvout = csv.writer(fp)
csvout.writerow(column_name) # 初次爬取写入列名
csvout.writerow(data)
else:
# 追加写入内容
with open(out_dir, "a", newline='', encoding='utf-8') as fp:
csvout = csv.writer(fp)
csvout.writerow(data)
def parse_house_page(response):
"""
解析房子网页获得需要的数据
"""
data=response.text
try:
# title
x1 = se(text=data).xpath(
'//span[@class="tit_text"]/text()').extract_first()#.replace('\n', '').strip()
# 总价
x2 = se(text=data).xpath(
'//div[@class="trl-item price_esf sty1"]/i/text()').extract_first()
# 户型
x3 = se(text=data).xpath(
'//div[@class="tt"]/text()').extract()[0].replace('\n', '').strip()
# 建筑面积
x4 = se(text=data).xpath(
'//div[@class="tt"]/text()').extract()[1]
# 朝向
x5 = se(text=data).xpath(
'//div[@class="tt"]/text()').extract()[3]
# 楼层
x6 = se(text=data).xpath(
'//div[@class="tt"]/a/text()').extract()[0]
# 总楼层
x7 = se(text=data).xpath(
'//div[@class="font14"]/text()').extract()[4]
# 装修
x8 = se(text=data).xpath(
'//div[@class="tt"]/a/text()').extract()[1]
# 所在小区
x9 = se(text=data).xpath(
'//div[@class="rcont"]/a/text()').extract_first()
# 区
x10 = se(text=data).xpath(
'//div[@id="address"]/a/text()').extract()[0].replace('\n', '').strip()
# 区域
x11 = se(text=data).xpath(
'//div[@id="address"]/a/text()').extract()[1].replace('\n', '').strip()
# 建筑年代
year = se(text=data).xpath(
'//span[text()="建筑年代"]/../span/text()').extract()
if year:
x12 = year[1]
else:
x12 = None
# 有无电梯
x13 = se(text=data).xpath(
'//span[text()="有无电梯"]/../span[@class="rcont"]/text()').extract_first()
# 挂牌时间
date = se(text=data).xpath(
'//span[text()="挂牌时间"]/../span[@class="rcont"]/text()').extract_first()
if date:
x14 = date.replace('\n', '').strip()
else:
x14 = date.extract_first()
# 小区参考均价
x15 = se(text=data).xpath(
'//span[text()="参考均价"]/../span/i/text()').extract_first()
# 绿化率
x16 = se(text=data).xpath(
'//span[text()="绿 化 率"]/../span[@class="rcont "]/text()').extract_first()
# 容积率
x17 = se(text=data).xpath(
'//span[text()="容 积 率"]/../span[@class="rcont "]/text()').extract_first()
# 建筑类型
x18 = se(text=data).xpath(
'//span[text()="建筑类型"]/../span[@class="rcont "]/text()').extract_first()
# 人车分流
x19 = se(text=data).xpath(
'//span[text()="人车分流"]/../span[@class="rcont "]/text()').extract_first()
# 总户数
x20 = se(text=data).xpath(
'//span[text()="总 户 数"]/../span[@class="rcont "]/text()').extract_first()
# 总楼栋数
x21 = se(text=data).xpath(
'//span[text()="总楼栋数"]/../span[@class="rcont "]/text()').extract_first()
# 评分
x22 = se(text=data).xpath(
'//p[text()="交易价值"]/../h4/text()').extract_first()
x23 = se(text=data).xpath(
'//p[text()="居住品质"]/../h4/text()').extract_first()
x24 = se(text=data).xpath(
'//p[text()="便利指数"]/../h4/text()').extract_first()
x25 = se(text=data).xpath(
'//p[text()="物业服务"]/../h4/text()').extract_first()
x26 = se(text=data).xpath(
'//p[text()="房源评级|多维度分析房源"]/../div[@class="num_bg"]/h3/text()').extract_first()
# 单价
x27 = se(text=response.text).xpath(
'//div[@class="tt"]/text()').extract()[2]
data = [x1, x2, x3, x4, x5, x6,
x7, x8, x9, x10, x11, x12,
x13, x14, x15, x16, x17, x18,
x19, x20, x21, x22, x23, x24,
x25, x26, x27]
save(data, out_dir,columns)
print(f"房源{response.url}保存成功")
except:
fail_url.append(response.url)
save([response.url], 'fail_url.csv',['链接地址']) # 字符串写入时会默认拆开
print(f"{response.url}解析失败,请稍后查看实际情况,当前解析失败网页总数为{len(fail_url)}")
def main(url):
response = get_url(url)
true_url=process_url(response)
response=get_url(true_url)
house_urls, next_url = parse_page(response)
while(next_url):
for url in house_urls:
response = get_url('https://xian.esf.fang.com'+url)
true_url=process_url(response)
response=get_url(true_url)
parse_house_page(response)
response=get_url(next_url)
true_url = process_url(response)
response = get_url(true_url)
house_urls, next_url = parse_page(response)
if not(next_url):#将最后一页数据进行解析
for url in house_urls:
response=get_url(url)
true_url=process_url(response)
response=get_url(true_url)
parse_house_page(response)
if __name__ == '__main__':
start = time.time()
main('https://xian.esf.fang.com/house/i31/')
#多线程进行有一些问题,仍待进一步修改
""" url_list = ['https://xian.esf.fang.com/house/i31/',
'https://xian.esf.fang.com/house/i325/',
'https://xian.esf.fang.com/house/i350/',
'https://xian.esf.fang.com/house/i375/']
for url in url_list:
_thread.start_new_thread(main,(url,)) """
end = time.time()
print("运行程序花费了%s秒" % (end-start))虽然会因为爬取频率过高导致网页请求出现拖动滑块的验证码,但是真的不想改了啊啊很棒了。
第二次完善之后还是有好多问题😭,多线程爬取数据存在明显问题,还有待进一步完善。不过记得下次可以把try语句写进匹配规则里,因为不知道会遇到什么问题,这是一种很稳妥的渐进爬虫。多线程属于一个相对比较复杂的问题,这块因为本身计算机理论的缺失可能还有待进一步补充,照目前的情况来看,写的爬虫的爬取效率还有待进一步提升,再者就是访问次数过多出现的滑块验证问题还没有解决。