地理信息可视化能力建设教程--爬虫能力-安居客

546 阅读5分钟

如何爬虫--1 安居客

注意! 本教程所做工作仅用于支持地理信息数据分析。

如果你需要地理信息数据分析那么爬虫应该是一个必修课。我们可以将数据从不同的网站抓取下来进行分析。爬虫其实任何语言都可以实现,只要你能请求网络,能够解析页面那么你就可以爬虫。我们呢~将循序渐进来学习如何爬虫以及如何运用数据为地理信息分析服务。下面开始!

1 爬虫基础

1.1 html

html是网页页面的基础,我们节选w3school上面的基本介绍。
1)HTML 不是一种编程语言,而是一种_标记语言_ (markup language)。
2)HTML 标签通常是_成对出现_的,比如 ,第一个标签是_开始标签_,第二个标签是_结束标签_
通过一系列标签的组合完成了整个页面的表述。

之后每个标签的作用会在遇到的时候在进行讲解,感兴趣的可以自己先写写页面试试看,有助于对html的理解。 www.w3school.com.cn/

<html><!-- 根标签-->
  <head><!-- 头标签,存相关meta 信息等-->
  </head>
<body><!-- body标签,存页面内容js等-->

<h1>我的第一个标题</h1><!-- h1标签,存标题-->

<p>我的第一个段落。</p><!-- p标签,存文字-->

</body>
</html>

以上的页面时简单的html示例,讲解基本结构以及几个标签。

1.2 CSS 选择器

CSS选择器简单来说就是,用于选择节点元素的,它在爬虫中主要用于选择对应的节点截取数据。

选择器 例子 例子描述
.class .intro 选择 class="intro" 的所有元素。
#id #firstname 选择 id="firstname" 的所有元素。
element p 选择所有

元素。

element_element div,p 选择所有
元素和所有

元素。

element_element div p 选择
元素内部的所有

元素。

element_element div>p 选择父元素为
元素的所有

元素。

element_element div+p 选择紧接在
元素之后的所有

元素。

[attribute] [target] 选择带有 target 属性所有元素。
[attribute_value] [target=_blank] 选择 target="_blank" 的所有元素。
[attribute_value] [title~=flower] 选择 title 属性包含单词 "flower" 的所有元素。
[attribute_value] [lang|=en] 选择 lang 属性值以 "en" 开头的所有元素。
[attribute_value] a[src^="https"] 选择其 src 属性值以 "https" 开头的每个 元素。
[attribute_value] a[src$=".pdf"] 选择其 src 属性以 ".pdf" 结尾的所有 元素。
[attribute_value] a[src*="abc"] 选择其 src 属性中包含 "abc" 子串的每个 元素。
:nth-child(n) p:nth-child(2) 选择属于其父元素的第二个子元素的每个

元素。

:nth-last-child(n) p:nth-last-child(2) 同上,从最后一个子元素开始计数。
:nth-of-type(n) p:nth-of-type(2) 选择属于其父元素第二个

元素的每个

元素。

:nth-last-of-type(n) p:nth-last-of-type(2) 同上,但是从最后一个子元素开始计数。

以上是从w3school中截取的几个主要的CSS选择器,之后在具体实战是有实际的运用示例。

2 工具

爬虫是要有工具的,这个工具必须简单快捷上手。

2.1 node.js

node.js 是一个javascript的后端语言,为啥用它呢?
1 简单 js上手很快
2 库多,js有很多方便解析的工具库,不用爬虫框架自己写灵活度高,还有助于学习网页相关开发技术。
具体安装细节为:
nodejs.org/en/

图片.png

下载的时候,看这里,这里有两个版本,我比较喜欢新版本所以选右边的下载下来点击运行。
下载的是windows的msi文件。

点击run运行


点击next下一步

接收 下一步

选择合适的安装目录 下一步

建议全部安装下一步,其中npm是nodejs的包管理工具

安装

等待......

完成

测试一下

图片.png

2.2 nodejs爬虫工具库cheerio


www.npmjs.com/package/che…
这个库是一个html解析库能够将爬取的页面进行解析,并通过类似jquery的语法将有效的信息抓取出来。

3 流程

其实爬虫的流程肥肠简单!
1 下载页面
2 解析页面
3 存储解析出的信息
一切的爬虫其实都是由这三部发展而来!!!!!!大家可以自己思考一下!!!

4 准备

wh.zu.anjuke.com/
**这是我们要爬取的网址武汉安居客,因为我最近要租房... ... **
4.1 查看页面

首先打开页面我们看看有什么
小技巧: 浏览器F12键可以打开调试窗口用于调试(推荐使用火狐或chrome浏览器)。

图片.png

如图所示,1部分是我们要抓取的内容,2部分是F12打开的浏览器调试窗口。其中查看器用于查看页面元素,控制台用于查看js代码输入输出,调试器用于查看。
图片.png

通过查看器就可以寻找对应的页面元素,具体方法是点击坐标那个小尖角开启查看器模式,如下图所示。
图片.png

点击打开之后就可以在html页面上滑动,下面的查看器面板就回显示对应的html节点。右键编辑html就可以查看某一个节点内的所有的html。
图片.png

4.2 分析页面

<div class="zu-itemmod" link="https://wh.zu.anjuke.com/fangyuan/1314369360?shangquan_id=17888" _soj="Filter_3&amp;hfilter=filterlist">
    <a data-company="" class="img" _soj="Filter_3&amp;hfilter=filterlist" data-sign="true" href="https://wh.zu.anjuke.com/fangyuan/1314369360?shangquan_id=17888" title="康卓新城 无中介 光谷大道sbi创业街  朝南采光好 可月付" alt="康卓新城 无中介 光谷大道sbi创业街  朝南采光好 可月付" target="_blank" hidefocus="true">
      <img class="thumbnail" src="https://pic1.ajkimg.com/display/b5fdc13f19381f593b46baada1237197/220x164.jpg" alt="康卓新城 无中介 光谷大道sbi创业街  朝南采光好 可月付" width="180" height="135">
      <span class="many-icons iconfont"></span></a>
    <div class="zu-info">
      <h3>
        <a target="_blank" title="康卓新城 无中介 光谷大道sbi创业街  朝南采光好 可月付" _soj="Filter_3&amp;hfilter=filterlist" href="https://wh.zu.anjuke.com/fangyuan/1314369360?shangquan_id=17888">康卓新城 无中介 光谷大道sbi创业街 朝南采光好 可月付</a></h3>
      <p class="details-item tag">1室1厅
        <span>|</span>47平米
        <span>|</span>低层(共24层)
        <i class="iconfont jjr-icon"></i>占高锋</p>
      <address class="details-item">
        <a target="_blank" href="https://wuhan.anjuke.com/community/view/1039494">康卓新城</a>&nbsp;&nbsp; 洪山-光谷 雄楚大道694号</address>
      <p class="details-item bot-tag">
        <span class="cls-1">整租</span>
        <span class="cls-2">东南</span>
        <span class="cls-3">有电梯</span>
        <span class="cls-4">2号线</span>
      </p>
    </div>
    <div class="zu-side">
      <p>
        <strong>1600</strong>元/月</p></div>
  </div>

我们通过上面的图片以及查看html结构发现,每一页的租房信息都是通过列表来进行组织的,这个时候就需要将其中的一套租房信息的代码拿出来进行分析如上面的代码所示。
我们能够一个房屋段落标签中看到这里面包括一套房屋所有的相关的信息,这个时候就开始寻找相关节点,
这就要和页面进行搏斗了,我们需要思考的问题是,我们要找的那些节点怎么弄出来,就要用到神奇的CSS选择器!
我们看下面截取的代码,结合代码来进行研判。(如果大家需要每一个选择器都讲请留言,我会在下一个教程中讲解的)

const { URL } = require('url'); 
const cheerio = require('cheerio');

function dealhtml(text){
    const $ = cheerio.load(text);//将读取的内容转换为可以解析的对象
    let arrs = $('.zu-itemmod');//选择所有的.zu-itemmod选择器节点内的内容
    let finalresult = [];//所有数据的数组
  //在$('.zu-itemmod')里面包含着所有选择器为.zu-itemmod的节点,cheerio需要使用each函数来进行遍历
  //找寻里面的节点 回调中使用el代表返回的节点
    $('.zu-itemmod').each(function (index, el) {
      let result = { //存储每一个子数据的对象
  
      };
      let $el = $(el)//将一个.zu-itemmod在进行解析用于提取对象
      let url = $el.attr("link"); //提取出.zu-itemmod节点的link属性提取URL
      let idurl = new URL(url);//放入NODE的URL对象也可以自己解析
      let pathname = idurl.pathname;  
      //https://wh.zu.anjuke.com/fangyuan/1314369360?shangquan_id=17888 
      //从url中提取出id 提取ID很重要哦 要不然你没法去重
      let paths = pathname.split("/");
      result.uid=paths[2];  //从url中解析唯一id 
      result.url=url; //保留一个连接用于更细致的解析
    //   console.log(`--------------------------------  `)
    //   console.log(` 地址      ${result.uid}  `)
    //   console.log(` 唯一主键     ${result.url}  `)
      let info = $el.find('.details-item.tag').text(); 
      //<p class="details-item tag">
      //找到这一个部分 两个类的在cheerio中要紧贴着写 text函数用于获得对应的文字去掉标签
      //
      let infoarr = info.replace("\n","").trim().split("");
      //我们可以通过调试来查看解析的内容然后 通过以上的方式让他们分开
      infoarr[0]=infoarr[0].trim();
      result.owner = infoarr[1]; //所有者
      infoarr=infoarr[0].split("|");
      result.apartment = infoarr[0];//户型
      result.totalarea = infoarr[1];//全部面积
      result.floortype = infoarr[2];//楼层类型
      //遍历信息解析房屋涉及的相关信息
    //   console.log(` 所有者     ${result.owner}  `)
    //   console.log(` 户型       ${result.apartment}  `)
    //   console.log(` 全部面积   ${result.totalarea}  `)
    //   console.log(` 楼层类型   ${result.floortype}  `)
      let address = $el.find("address[class='details-item']").text().trim()
      //解析! 这里是使用address标签其中类属性是details-item的元素 
      //获取文字之后去掉两边空格
      address = address.replace("\n","").split(" ");
      result.address0 = address[0];
      result.address1 = address[address.length-2];
      result.address2 = address[address.length-1];
    //   console.log(` 小区名   ${result.address0}  `)
    //   console.log(` 地址1    ${result.address1}  `)
    //   console.log(` 地址2    ${result.address2}  `)
      result.rent = $el.find('.cls-1').text()//整租 合租 
      //解析!! 查找那个类选择器为.cls-1的节点 从中解析出文字 下面的几个都一样。
    //   console.log(` 整租合租   ${result.rent}  `)
      result.orientation = $el.find('.cls-2').text()
    //   console.log(` 朝向   ${result.orientation}  `)//朝向
      result.elevator = $el.find('.cls-3').text()    //是否有电梯
    //   console.log(` 电梯   ${result.elevator}  `)
      result.metro = $el.find('.cls-4').text()
    //   console.log(` 地铁   ${result.metro}  `)//地铁
      let price = $el.find('.zu-side').text()
      result.price=price.trim()
    //   console.log(` 价格   ${result.price}  `)
      finalresult.push(result)
    });
    return finalresult;
    // console.log(arrs);
  }

!!!小技巧!!!

爬取数据的时候能找到主键要找到主键!!!! 为以后去重方便

4.3 请求数据

我们现在,要思考的就是请求数据的部分,请求数据其实,对于安居客这种静态生成页面很简单,直接请求就行,暂时还不需要太多奇技淫巧。
我们使用node.js中的https工具包
nodejs.org/api/https.h…
我们使用其中的get函数

图片.png

我们看到哦,这个就是https的get方法包括三个参数url,配置对象,以及回调函数。
配置对象具体和http的get是一样的如图
nodejs.org/api/http.ht…
图片.png

最好要会看文档哦,随着我们的学习,这里有些参数是需要用到的哦。
目前用到的是headers参数也就是http请求头设置,http首部中包含着请求头信息。这些信息能够,在缓存、字符集等方面提供帮助。

/**
 * https请求头
 */
const options = {
  headers :{
    "Accept"	:"text/html",
    "Accept-Encoding" :"utf-8",
    "Accept-Language":	 "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
    "Cache-Control"	:"max-age=0",
    "Connection":	"keep-alive",
    "Cookie":	"aQQ_ajkguid=51C5A875-EDB6-391F-B593-B898AB796AC7; 58tj_uuid=c6041809-eb8d-452c-9d70-4d62b1801031; new_uv=2; __xsptplus8=8.2.1556593698.1556593730.4%233%7Ccn.bing.com%7C%7C%7C%7C%23%23tCD6uK-qprKqvfUShNPiQBlWlzM8BP6_%23; als=0; ctid=22; wmda_uuid=f0aa7eb2fd61ca3678f4574d20d0ccfc; wmda_new_uuid=1; wmda_visited_projects=%3B6289197098934; sessid=FE2F0B80-FDC6-0C84-22E5-1448FB0BFA2C; lps=http%3A%2F%2Fwh.xzl.anjuke.com%2Fzu%2F%3Fkw%3D%25E4%25BF%259D%25E5%2588%25A9%25E5%259B%25BD%25E9%2599%2585%25E5%2585%25AC%25E5%25AF%2593%26pi%3D360-cpcjp-wh-chloupan1%26kwid%3D16309186180%26utm_term%3D%25e4%25bf%259d%25e5%2588%25a9%25e5%259b%25bd%25e9%2599%2585%25e5%2585%25ac%25e5%25af%2593%7Chttps%3A%2F%2Fcn.bing.com%2F; twe=2; ajk_member_captcha=4b7a103be89a56fd6fdf85e09f41c250; wmda_session_id_6289197098934=1556593697467-eaf2078d-1712-7da9; new_session=0; init_refer=; wmda_uuid=f4d4e8a6e26192682c1ec6d3710362b7; wmda_new_uuid=1; wmda_session_id_6289197098934=1556593697467-eaf2078d-1712-7da9; wmda_visited_projects=%3B6289197098934",
    "Host":	"wh.zu.anjuke.com",
    "Upgrade-Insecure-Requests":	1,
    "User-Agent"	:"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:66.0) Gecko/20100101 Firefox/66.0"
  }
};

/**
 * 用于获取数据
 * @param {*} url 
 */
function getdata(url){

   url = new URL(url);
   console.log(url)
   url.headers = options.headers;
    https.get(url,(res) => {
      console.log('状态码:', res.statusCode);
      res.setEncoding('utf8');
      let rawData = '';
      res.on('data', (chunk) => { 
        rawData += chunk; 
      });
      res.on('end', () => {
        try {
          let result = dealhtml(rawData)
          exporttoFile(result,url.href)
        } catch (e) {
          console.error(e.message);
        }
      });
    }).on('error', (e) => {
      console.error(e);
    });
}

!!!小技巧!!!
爬虫时请求头中的User-Agent用于识别你是不是机器爬取,初级爬虫技巧我们要自己修改这个参数。方法是从任意一个网络请求中复制一个即可,如下图所示。

图片.png

4.4 存储数据

function exporttoFile(obj,filename){
  filename = filename.replace("https://wh.zu.anjuke.com/","").split("/").join('_');
  let data = JSON.stringify(obj);
  fs.writeFileSync(`./result/${filename}.json`,data);
}

当然我们收集到了数据就要把它存储起来,这样才能利用,我们将以上的结果转为文字存储到json文件中,文件名称随意啦。

4.5 速度控制

速度控制很重要,如果不做速度控制那么会被服务器查出来然后被封IP。所以要掌握节奏每隔一段时间来进行爬取。这里使用ES6语法 async/await 来进行控制
具体操作如下 await sleep(2000); 函数输出了一个Promise对象,在通过 async/await 语法,使其同步执行强制等待两秒。
!!!小技巧!!!
这里的限速是初级爬虫技巧,之后还有高级的正在加班中。

async function sleep(time = 0) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve();
      }, time);
    })
 }

async function start(){
  // let url = "https://wh.zu.anjuke.com/fangyuan/wuchanga/x1/";
  let urls = getUrl();
  for(let i=0;i<urls.length;i++){
    await sleep(2000);
    console.log(urls[i])
    getdata(urls[i]);
  }
}

4.6 生成URL

接下来就是URL的拼接啦,拼接URL主要是应对翻页的情况,请各位自己观察翻页之后的URL进行调整。重点就是观察规律。



 let position = ['wuchanga'];
 let rent  = "x1";
   function getUrl(){
    let result = [];
    let url = "https://wh.zu.anjuke.com/fangyuan";
    for (let i=0;i<30;i++){
        let surl = url+"/"+position+"/"+rent+"-"+`p${i}`+"/";
        result.push(surl)
    }
    return result;
   
 }

5 最终的工程

其实,根据之前的代码细节,我们就可以写出爬虫工程。
我们已经将爬虫工程放到了git上供大家学习。
github.com/yatsov/craw…
欢迎star。
如果有不明白的欢迎fork可以提issue 我们一起讨论 或者是在公众号中留言。
注意哦,入库是index文件。

6 贡献团队简介:

作者:

张健 武大资环理论与方法实验室 主要方向 地理信息软件工程与时空数据可视化

审稿:

童莹:武大资环理论与方法实验室 潘昱成:武大资环自然地理专业

欢迎关注地图可视化公众号!

微信号 : MapVis

公众号图片