巧用web机器人搜索廉价二手房
Posted redguardtoo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了巧用web机器人搜索廉价二手房相关的知识,希望对你有一定的参考价值。
1. 问题
我需要一套二室一厅,价格在40万左右的,位于上海某几个区(例如徐汇,闵行区)的二手房。我理想中的房子当然需要满足很多条件,例如它必须是两室都朝南的房子,它的均价必须低于7000元每平方米,等等。
通常我使用上海热线的二手房网(http://secondhand.online.sh.cn/)来查找最新的二手房信息。
但是每天跟踪网站的最新二手房信息太累人了。二手房网每天都要添加很多最新信息,为了识别出符合我的条件的房子,我必须把所有这些信息都看一遍。整个过程非常浪费时间。
原因在于:
1. 网站只会在首页显示最新加入的一些二手房的*概要*信息。而我为了作出自己的判断,光是看概要信息是不够的,我必须看详细信息。而这就意味着我必须点击概要信息中的名为“详细”的超链接来下载详细信息的页面。
2. 并不是所有信息都是放在一个页面上的。事实上,上海热线的二手房网的信息是分页显示的,每个页面大概只显示10条信息。如果我需要看所有的信息的话,我需要看大概500-3000条信息左右(信息的多少和搜索条件是否严格,搜索的区域的大小有关系)。
上述1,2点的意思就是,即使我只需要看500条信息,我也需要看550个页面:
500/10(概要信息页面)+500*1(每条信息的详细页面)=550
每个页面载入的时间是10秒(实际取样的结果),我阅读每个页面的时间是25秒(我的阅读速度是非常快的了)。
550*(10+25)=19250秒=320分钟=5小时。
也就是说,光是阅读信息我就需要花5小时的时间。事实上,我还没有考虑对信息进行分析(例如列出均价最低的10处房产)的时间呢,这远比阅读更花时间。
2. 问题的本质
由于现在很多信息都放在网上了,类似的问题很常见。例如招聘,证券等等,都有同样的问题。这个问题就是,如何高效率地从internet上获得数据并进行分析。
3.一个方案
我看了一些国内的相关文章,这些文章的方案都是基于IE的自动化的。通常使用VB语言加上WebBrowser控件。
这种方案最主要的缺点是由于WebBrowser对很多数据操作进行了封装,使得本来很简单的操作变得麻烦无比(我猜原因是因为微软设计WebBrowser的目的是自动化IE的操作)。例如取得当前页面这样一个操作,如果使用WebBrowser开发的话竟然需要分取得当前页面,在某个事件中获得取得页面成功两个进程。为了使这两个进程通信又需要很多额外的工作。
我的方案是使用python开发一个脚本,因为python的库支持比较完备,这样还可以获得跨平台的额外好处。我使用结构化的设计,因为使用web机器人取得数据这样的事情本身就是结构化的。我将和具体网站相关的操作都封装在某几个函数内,这意味着该脚本如果经过适当的改写,还能有其他应用。也就是,这个脚本不仅仅是用于上海热线二手房网的,也不仅仅是先于搜索二手房的。
在写这个脚本之前,我已经有一些边写web机器人的经验,使用的语言包括VB,Java,Python等等。我认为目前我写的这个web机器人是比较成熟的。
这个方案的要点是这样的,首先向二手房网提交条件,然后获得分页搜索结果,从第一页中读取各信息条款(),读取共有多少页面等等必要的信息(这些信息的读取同样需要硬编码起始标志和结束标志)等等。然后根据这些信息模拟翻页,点击“详细”链接等动作。这些动作无非就是http标准中的get,post动作而已。
上述方案中的某些地方需要硬编码,必要的话需要分析浏览器客户端脚本(例如javascript):
*各信息条款的起始标志和结束标志
*当前页面计数的起始标志和结束标志
*页面总数的起始标志和结束标志
*翻页动作中的超链接地址
*点击“详细”超链接中的超链接地址
获得的数据我都打印在屏幕上,这些数据使用逗号分割,如果希望把数据导入到文件中,可以使用管道机制。
例如,假设我写的脚本是house.py,你希望把house.py的数据导入到文件house.csv中去,你可以输入这样的命令:
python house.py > house.csv
接下来你可以就可以分析house.csv了。我通常用支持正则表达式的编辑器去掉house.csv中的一些格式化字符,再用excel进行数据分析。
4. 具体实现以及代码
具体实现中还碰到一些小问题。例如为了支持中文,我必须再脚本的开头加上“# -*- coding: mbcs -*-”这一行。我上网需要使用指定的代理服务器,所以我使用urllib2库而不是urllib库。这些问题都已经在源代码中说明了。
下面就是源代码:
1 # -*- coding: mbcs -*- 2 3 #------------------user configuration------------begin 4 5 #是否使用需要验证的http代理?(仅限于极少数公司,大多数情况下置为0) 6 config_using_proxy=0 7 8 #代理地址和端口号,仅当config_using_proxy等于1时有效 9 config_httpproxy="" 10 11 #登陆代理服务器的用户名,仅当config_using_proxy等于1时有效,否则可以忽略 12 config_username="" 13 14 #登陆代理服务器的密码,仅当config_using_proxy等于1时有效,否则可以忽略 15 config_password="" 16 17 #选择所在区县("","闵行","徐汇","杨浦","虹口","长宁","静安","卢湾","黄浦","闸北","普陀","浦东","宝山","嘉定","青浦","奉贤","南汇","金山","松江","崇明"),""表示所有 18 config_szqx="虹口" 19 20 #选择几室? ("", "1","2","3","4","5") 21 config_fx_room="2" 22 23 #选择几厅? ("","0","1","2","3","4") 24 config_fx_hall="1" 25 26 #选择总价下限(单位:万),("0","10","15","20","25","30","35","40","50","60","70","80","90","100"),"0"表示不限 27 config_jg_min="20" 28 29 #选择总价上限(单位:万),("10000000","10","15","20","25","30","35","40","50","60","70","80","90","100"),"10000000"表示不限 30 config_jg_max="60" 31 32 #选择交易方式, ("0","1"), 0表示出售,1表示置换 33 config_type="0" 34 35 #登记日期(单位,天),("15","30","180",""),""表示不限 36 config_djrq="15" 37 38 #搜索表格第一行开始的标志字符串(第一行从该字符串之后开始搜索行开始标志) 39 config_tbl_begin_str=">区县<" 40 41 #搜索表格最后一行末尾的标志字符串(最后一行从该字符串之前开始反向搜索行结束标志) 42 config_tbl_end_str="共找到符合条件的出售房源信息" 43 #------------------------user configuration---------------end 44 45 46 #------------------------administrator configuration------begin 47 48 config_post_data={"szqx":config_szqx,"fx_room":config_fx_room,"fx_hall":config_fx_hall,"jg_min":config_jg_min,"jg_max":config_jg_max,"type":config_type,"djrq":config_djrq,"sortfield":"djrq","sorttype":"desc","whichpage":"1"} 49 50 #------------------------administrator configuration------end 51 52 53 54 55 from string import * 56 import sys 57 58 #-----------------print routines-------------begin 59 def dump_row_end(): 60 sys.stdout.write('/n') 61 62 63 def dump_table_begin(): 64 sys.stdout.write("区县"+",") 65 sys.stdout.write("物业地址"+",") 66 sys.stdout.write("房型"+",") 67 sys.stdout.write("物业类型"+",") 68 sys.stdout.write("建筑面积"+",") 69 sys.stdout.write("总价"+",") 70 sys.stdout.write("登记时间"+",") 71 sys.stdout.write("房源编号"+",") 72 sys.stdout.write("物业名称"+",") 73 sys.stdout.write("房 龄"+",") 74 sys.stdout.write("产权说明"+",") 75 sys.stdout.write("中介报价"+",") 76 sys.stdout.write("物业详细地址"+",") 77 sys.stdout.write("房屋朝向"+",") 78 sys.stdout.write("所在楼层"+",") 79 sys.stdout.write("装修程度"+",") 80 sys.stdout.write("室内条件"+",") 81 sys.stdout.write("有效期限"+",") 82 sys.stdout.write("备 注"+",") 83 sys.stdout.write("联 系 人"+",") 84 sys.stdout.write("联系电话"+",") 85 sys.stdout.write("/n") 86 87 88 def dump_one_field(str): 89 sys.stdout.write(str+",") 90 91 #-----------------print routines-------------end 92 93 94 #-----------------house parser-------------begin 95 def get_last_page_number(s): 96 no_begin=find(s,"尾页") 97 if no_begin==-1: 98 return 0 99 100 no_begin=rfind(s,"javascript:form_submit(/'",0,no_begin) 101 no_begin+=len("javascript:form_submit(/'") 102 no_end=find(s,"/'",no_begin) 103 if no_end==-1: 104 return 0 105 106 if no_begin>no_end: 107 return 0 108 return atoi(s[no_begin:no_end]) 109 110 111 def get_data_in_one_tag(instr4,tag): 112 tag_begin=find(instr4,"<"+tag) 113 if tag_begin==-1: 114 return instr4 115 116 tag_begin=find(instr4,">") 117 if tag_begin==-1: 118 return instr4 119 tag_begin+=1 120 121 tag_end=find(instr4,"</"+tag+">",tag_begin) 122 if tag_end==-1: 123 return instr4 124 125 return instr4[tag_begin:tag_end] 126 127 def filter_rubbish_data(str): 128 return strip(replace(replace(replace(replace(str,","," "),'/n',''),'/t',''),'/r','')) #maybe we will output data in csv format 129 130 def get_one_detailed_data(instr3,keyword): 131 132 #print instr3 #debug 133 #print keyword #debug 134 135 data_begin=find(instr3,keyword) 136 if data_begin==-1: 137 return "" 138 # handle data 139 data_begin=find(instr3,"<td",data_begin) 140 if data_begin==-1: 141 return "" 142 143 data_begin=find(instr3,">",data_begin) 144 if data_begin==-1: 145 return "" 146 data_begin=data_begin+1 147 148 data_end=find(instr3,"</td>",data_begin) 149 if data_end==-1: 150 return "" 151 152 if data_begin>data_end: 153 return "" 154 # delete space , comma ,tab and linefeed 155 #return replace(instr3[data_begin:data_end],","," ") 156 return filter_rubbish_data(instr3[data_begin:data_end]) 157 158 159 def get_detailed_data(instr2): 160 dump_one_field(get_one_detailed_data(instr2,"房源编号")) 161 dump_one_field(get_one_detailed_data(instr2,"物业名称")) 162 dump_one_field(get_one_detailed_data(instr2,"房 龄")) 163 dump_one_field(get_one_detailed_data(instr2,"产权说明")) 164 dump_one_field(get_one_detailed_data(instr2,"中介报价")) 165 #delete the href 166 tmpstr=get_one_detailed_data(instr2,"物业地址") 167 tmppos=find(tmpstr,"<a") 168 if tmpstr<>-1: 169 tmpstr=strip(tmpstr[:tmppos]) 170 dump_one_field(tmpstr) 171 dump_one_field(get_one_detailed_data(instr2,"房屋朝向")) 172 dump_one_field(get_one_detailed_data(instr2,"所在楼层")) 173 dump_one_field(get_one_detailed_data(instr2,"装修程度")) 174 dump_one_field(get_one_detailed_data(instr2,"室内条件")) 175 dump_one_field(get_one_detailed_data(instr2,"有效期限")) 176 dump_one_field(get_one_detailed_data(instr2,"备 注")) 177 dump_one_field(get_data_in_one_tag(get_one_detailed_data(instr2,"联 系 人"),"div")) 178 dump_one_field(get_data_in_one_tag(get_one_detailed_data(instr2,"联系电话"),"div")) 179 180 181 def get_data(instr,tbl_begin_str,tbl_end_str): 182 #table begin 183 idx=find(instr,tbl_begin_str) 184 if idx==-1: 185 return 186 idx=find(instr,"<tr",idx) 187 if idx==-1: 188 return 189 table_begin=idx 190 #print instr[table_begin:table_begin+100] #debug 191 192 #table end 193 idx=find(instr,tbl_end_str,table_begin) 194 #print instr[table_begin:idx] 195 if idx==-1: 196 return 197 idx=rfind(instr,"</tr>",table_begin,idx) 198 if idx==-1: 199 return 200 table_end=idx+len("</tr>") 201 #print instr[table_begin:table_end] #debug 202 203 #search rows 204 tr_idx=table_begin 205 while tr_idx<table_end: 206 #tr begin 207 tr_idx=find(instr,"<tr",tr_idx) 208 if tr_idx==-1: 209 return 210 tr_idx=find(instr,">",tr_idx) 211 if tr_idx==-1: 212 return 213 tr_begin=tr_idx+1 214 215 #tr end 216 tr_idx=find(instr,"</tr>",tr_begin) 217 if tr_idx==-1: 218 return 219 tr_end=tr_idx 220 #print instr[tr_begin:tr_end] #debug 221 222 223 #search cells in one row 224 td_idx=tr_begin 225 is_really_a_row_dumped=0 226 while td_idx<tr_end: 227 # td data begin 228 td_idx=find(instr,"<td",td_idx) 229 #print td_idx #debug 230 if td_idx==-1: 231 return 232 td_idx=find(instr,">",td_idx) 233 #print td_idx #debug 234 if td_idx==-1: 235 return 236 tddata_begin=td_idx+1 237 238 # td data end 239 td_idx=find(instr,"</td>",td_idx) 240 #print td_idx #debug 241 if td_idx==-1: 242 return 243 tddata_end=td_idx 244 245 if tddata_begin>tddata_end: 246 continue 247 248 if tddata_end>tr_end: 249 continue 250 251 if tddata_end>table_end: 252 continue 253 254 tddata=filter_rubbish_data(instr[tddata_begin:tddata_end]) 255 256 #if the tddata is a href, let's get more data from the href 257 href_begin=find(tddata,"href=/"javascript:urll(/'") 258 if href_begin==-1: 259 dump_one_field(tddata) 260 continue 261 262 263 href_begin=href_begin+len("href=/"javascript:urll(/'") 264 265 href_end=find(tddata,"/'",href_begin) 266 if href_end==-1: 267 return 268 269 view_url="http://secondhand.online.sh.cn/"+tddata[href_begin:href_end] 270 #print view_url #debug 271 #dump_one_field(view_url) 272 273 view_result=urllib2.urlopen(view_url) 274 view_data=view_result.read() 275 #print "view_data="+view_data #debug 276 get_detailed_data(view_data) 277 is_really_a_row_dumped=1 278 279 if is_really_a_row_dumped: #sometimes, no td output 280 dump_row_end() 281 #-----------------house parser-------------end 282 283 284 def install_proxy(): 285 httpproxy=config_httpproxy 286 username=config_username 287 password=config_password 288 httpproxystring='http://' + username + ':' + password + '@' + httpproxy 289 290 # build a new opener that uses a proxy requiring authorization 291 proxy_support=urllib2.ProxyHandler({"http":httpproxystring}) 292 293 authinfo=urllib2.HTTPBasicAuthHandler() 294 opener = urllib2.build_opener(proxy_support, authinfo,urllib2.HTTPHandler) 295 296 # install it 297 urllib2.install_opener(opener) 298 299 #----------------main---------------------begin 300 if __name__=="__main__": 301 #get the page 302 import urllib2 303 import urllib 304 305 #using proxy 306 if config_using_proxy: 307 install_proxy() 308 309 f = urllib2.urlopen("http://secondhand.online.sh.cn/selllist.php",urllib.urlencode(config_post_data)) 310 #print f.headers #debug 311 s=f.read() 312 #print s #debug 313 314 #parse the html page 315 #s="<table><tr><td>data11</td><td>data12</td></tr><tr><td>data21</td><td>data22</td></tr></table>" #debug 316 #config_tbl_begin_str="<table>" #debug 317 #config_tbl_end_str="</table>" #debug 318 319 # print out the table header 320 dump_table_begin() 321 # print out the first page 322 get_data(s,config_tbl_begin_str,config_tbl_end_str) 323 324 # get the page size from the first page data 325 last_page=get_last_page_number(s) 326 # print out other pages (if exist) 327 for i in range(2,last_page): 328 config_post_data['whichpage']=str(i) 329 f = urllib2.urlopen("http://secondhand.online.sh.cn/selllist.php",urllib.urlencode(config_post_data)) 330 s=f.read() 331 get_data(s,config_tbl_begin_str,config_tbl_end_str) 332 333 #s="<td height=26>header1</td><td height=26>data1</td>" #debug 334 #print get_one_detailed_data(s,"header1") #debug 335 336 #print get_last_page_number("<a href=/"javascript:form_submit(/'51/',/'djrq/',/'desc/')/">尾页</a>") #debug 337 #----------------main---------------------end
以上是关于巧用web机器人搜索廉价二手房的主要内容,如果未能解决你的问题,请参考以下文章
巧用Vscode编辑器,快速编辑代码,教你一键写完一段代码,向合格的cv工程师前进
巧用Vscode编辑器,快速编辑代码,教你一键写完一段代码,向合格的cv工程师前进
巧用Vscode编辑器,快速编辑代码,教你一键写完一段代码,向合格的cv工程师前进
巧用Vscode编辑器,快速编辑代码,教你一键写完一段代码,向合格的cv工程师前进