1.目的
这个project用于爬取YorkBBS上完整的住房信息,并将其生成一个html,放入apache2的服务器下,使之可以在任何地方查看筛选好的信息。
2.网页解析以及数据接口获取
YorkBBS采用的是动态网页加载,如果采用http解析器直接采集DOM结构的话,只能得到一个参杂了JS代码的HTML结构。这里有两个思路:
使用诸如PhantomnJS之类的库,等待网页加载完成才进行DOM读取
使用Chrome DevTool,通过Network功能查找和服务器端的通讯从而判断出数据的API接口,并通过伪造Header获取相应的信息
这里我们采用第二个思路,首先打开Chrome DevTool 的Network标签,刷新目标网页,观察和服务器目标中的通讯。我们很快发现了这个Header:
1
2
3
4
5Request URL: http://house.yorkbbs.ca/house.search/api/Rent/Filter
Request Method: POST
Status Code: 200 OK
Remote Address: 104.25.35.111:80
Referrer Policy: no-referrer-when-downgrade
通过Response我们能够确认这是我们的目标信息:
1 | {"success":true,"result":{"rowCount":4289,"pageIndex":1,"pageCount":215,"list":[{"id":4000065874,"title":"DT湖边高层condo出租一单间","image":"https://i.ybbs.ca/media/large/37198eafc3d9d3ecda84ea4b9ad26b9c.jpeg","province":"Ontario","city":"Toronto","region":"Toronto","intersection1":"spadina","intersection2":"fortyork","price":1100.0,"liveInDate":"2018-09-01T00:00:00","isCanLiveInNow":false,"userType":0,"userId":840509,"userName":"larrrrry0721","refreshTime":"4分钟前更新","linkMan":"larrrrry0721","details":"禁养宠物 | 禁室内抽烟 | 少煮食 | 有家具 | 游泳池 | 健身房","detailList":{"1001":["Condo"],"1002":["单间"],"1003":["3房"],"1004":["1厅"],"1005":["无车位"],"1008":["3个月"],"1010":["禁养宠物","禁室内抽烟","少煮食","有家具","游泳池","健身房"]},"enName":"","priceNegotiable":false,"orderNum":1000}},"error":null} |
那么下一步是如何获取这些信息。
要获取相应的信息,我们只需要和正常对API接口进行请求一样就可以了,这里唯一的区别是我们并不知道该使用什么参数作为请求。再次检查Header,可以发现Header有个 Request Payload的属性,那么通过查看里面的内容。
1 | { |
只要把这个payload加入对网址的请求当中,就可以获得如上图的信息了。
那么如何加入更多的参数选项?首先要试探新的参数,随意改变一个参数,再次对网页请求response。
1 | { |
此时可以看到参数变化了,这意味着我们可以随意改变这些参数进行符合我们期望的请求。
我们的目的是一次性抓取YorkBBS上所有的住房信息,那么只要简单的将pageIndex改为1并且把pageSize改为超出目前所有住房信息的数量,就能轻松一次性抓取论坛上所有的住房信息。
首先载入NodeJS上的http解析器superagent。const request = require('superagent')
使用superagent的promise结构进行http请求
1 | request |
这里.type决定了请求的类型,而.send则决定了payload。在取得response后将response转为Json结构以供下一步使用。
3.读取并解析和过滤信息
1.读取信息
因为superagent采用了promise的结构,所以上一步resolve的结果直接传入下一步。
在这里我们观察json的结构,可以发现有
1 | text:"{"success":true,"result":{"rowCount":623,"pageIndex":1,"pageCount":623,"list":[{"id":4000063147,"title":"多伦多北约克屋美价廉独立屋长短期出租","image":"https://i.ybbs.ca/media/large/2910b4198098b5aefa560a6340f4703d.jpeg","province":"Ontario","city":"Toronto","region":"Toronto","intersection1":"bayview","intersection2":"finch","price":1190.0,"liveInDate":null,"isCanLiveInNow":true,"userType":0,"userId":112843,"userName":"cmidc","refreshTime":"44分钟前更新","linkMan":"cmidc","details":"单身女性 | 单身男性 | 夫妻情侣 | 三口之家","detailList":{"1001":["独立屋"],"1002":["套间"],"1003":["1房"],"1004":["1厅"],"1005":["1车位"],"1006":["独卫"],"1008":["不限制"],"1009":["单身女性","单身男性","夫妻情侣","三口之家"],"1010":["禁养宠物","禁室内抽烟","有家具","可短租","包水电"]},"enName":"","priceNegotiable":false,"orderNum":1000}]},"error":null}" |
如果直接调用上一步的res.text,则实际得到的是一个String 而不是json object,所以我们还要进行进一步转换。将res赋值为res.text,并使用Json.parse(res)
将其转为json object。
2.解析信息
此时,我们可以发现这个json object中包含数个我们感兴趣的信息。
1 | city: "Toronto" |
比如detailList,title,region,city。下一步我们将获取这些信息并将其过滤。
3.过滤信息
我们采取正则表达式的方法来判断object中是否有包含我们感兴趣的信息。
这里先建立过滤规则:
1 | let pattern1 = new RegExp("整层","i"); //1002 整层 |
这里’”i”意味着无视大小写,而[23]房意味着选取2或者3房的条件,当我们面临多个过滤条件的时候可以采用|
隔开条件。
通过pattern.test(obj)我们可以得到一个true
或者false
的boolean
。
但是我们可以发现只有refreshTime
这一行是比较特殊的数据,当时间小于1小时的时候,它会返回xx分钟前更新
,而当时间小于一天的时候它会返回xx小时前更新
,当时间大于1天小于7天的时候它会返回xx天前更新
,而当时间大于7天时,它会返回2018年x月x日 更新
。
这使得数据解析变得困难,所幸我们的筛选条件是只需要30天以内的信息,那么我们关注的是最后一种数据格式。这里我们使用JS自带的Date()方法来进行数据比较。
1 | function dateMatch(date){ |
这里我们首先用正则方法筛选掉分,小时和天的数据,只留下日期格式。首先将多余的日 更新
字符去掉,然后使用replace方法替换中文的部分成-
。这样数据就是标准的日期格式了。然后使用JS自带的Date.parse方法就能够得到一个从1971年1月1日到该天数的毫秒数。同理,使用Date()方法取得现在的时间,并将其转换为毫秒数,let days = Math.floor((today-olddate)/86400000);
这里86400000是一天的毫秒数。这样我们就得到了天数,将其与条件比较,不符合的抛弃。
4.生成HTML
此时符合条件的数据已经筛选完毕,将其生成HTML。