现在的位置: 首页 > 综合 > 正文

巧用web机器人搜索廉价二手房

2013年02月26日 ⁄ 综合 ⁄ 共 10579字 ⁄ 字号 评论关闭

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

抱歉!评论已关闭.