• 从人人网抓取高校数据信息,包括,省份 高校 院系 (提供最终SQL文件下载)


    从人人网抓取高校数据信息,包括,省份 - 高校 - 院系 

    Author: handt

    Blog    : www.cnblogs.com/handt

    涉及到的脚本和 SQL 文件 点此下载

    =========== 正文部分 ==============

    思路分析:

    1. 获得高校数据

    地址:http://s.xnimg.cn/a44177/allunivlist.js (通过分析页面请求确定为该文件)

    对该文件进行人工分析,结合页面弹出框,可以得知,该js文件包含了 国家、城市、学校 信息。文件为 “非标准” json格式:所有key值均未加双引号。比如其中大学信息格式为:

    {id:1,univs:[{id:1001,name:"\u6e05\u534e\u5927\u5b66"}

    标准 JSON 格式的id,univs,name 都应该用双引号包围。关于JSON格式的简单介绍可以参见《PHP 与 JSON》

    因此,第一步 应该是格式化数据文件,转换为标准JSON格式,以便直接用PHP的 json_decode 函数处理。

    接下来,具体分析该文件的字段信息。

    出于提高访问速度的考虑,allunivlist.js 文件被压缩在同一行。为了快速分析该文件,提供一个小技巧:将文件另存到本地,用vim打开,使用 vim 的括号匹配功能(shift+5)从最外层逐渐开始分析。(vim更多知识可以参见《Vimtutor拾遗》

    经过分析,文件整体结构为

    [{国家1},{国家2},{国家3}....]

    国家定义:{id:xxx, univs:xxxx, name:xxxx, provs:xxxx, country_id:xxx}  (provs表示省份)

    provs:[{省份1},{省份2},{省份3}....]

    省份定义:{id:xxx, univs:xxx, country_id:xxxx, name:xxxx} (univs表示大学)

    univs: [{大学1},{大学2},{大学3}....]

    大学定义:{id:xxx, name:xxxx}

    通过该文件,能够获取到中国所有省份的高校信息。

    对应的,可以建立数据表school:

    1 CREATE TABLE `school` (
    2   `id` int(11) NOT NULL AUTO_INCREMENT,
    3   `sid` int(11) DEFAULT NULL COMMENT '学校id',
    4   `cid` int(11) DEFAULT NULL COMMENT '所在城市id',
    5   `name` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '名字',
    6   PRIMARY KEY (`id`)
    7 ) ENGINE=InnoDB AUTO_INCREMENT=3198 DEFAULT CHARSET=latin1;

    2. 获取各高校的院系信息

    人人网查询高校院系的接口为:http://www.renren.com/GetDep.do?id=高校ID

    这里的 高校ID 即为1中“大学定义”部分高亮标识的id,亦即数据表中的sid。

    该接口的返回信息很简单,一个<select>元素,其中的<option> value 就是院系信息。

    对于院系信息,可以建立数据表 department:

    1 CREATE TABLE `department` (
    2   `id` int(11) NOT NULL AUTO_INCREMENT,
    3   `sid` int(11) NOT NULL COMMENT 'school id',
    4   `did` int(11) NOT NULL COMMENT 'department id',
    5   `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'department 名字',
    6   PRIMARY KEY (`id`)
    7 ) ENGINE=InnoDB AUTO_INCREMENT=30044 DEFAULT CHARSET=latin1;

    确立了思路之后,下面开始动手。采用 PHP + MYSQL 完成编码部分内容。

    1. 获得高校数据:

    1.1 读取文件并将初始化为标准 JSON 格式

    将 http://s.xnimg.cn/a44177/allunivlist.js 文件另存到本地。

    因为这里只需要中国的数据,可以将 [{国家1},{国家2},{国家3}....] 中其他国家的内容删除。同时,开头的 ‘ var allUnivList=’ 也删掉。简单处理后,文件在括号匹配上是符合json格式的。

    接下来读取文件,对形如 ‘#(?P<name>\w+):#i’ 的数据,进行替换,在前后均添加双引号。

    读取文件和初始化代码:

     1 function x_readfile($filename)
     2 {
     3     $handle = fopen($filename, 'r');
     4     $contents = fread($handle, filesize($filename));
     5     fclose($handle);
     6     return $contents;
     7 }
     8 
     9 $content = x_readfile('allunivlist.js');
    10 // 将文件初始化为标准JSON格式
    11 $pat = '#(?P<name>\w+):#i';
    12 $replacement = '"${1}":';
    13 $str = preg_replace($pat, $replacement, $content);
    14 $ret = json_decode($str, true); // true 返回的是关联array类型,否则为class

    1.2 生成要插入数据库的语句:

    通过循环,生成形如 ‘insert into school(sid, cid, name) values(xxx, xxx, xxx)’的语句。

    $ret 变量是 json_decode 后的关联数组,回顾 1 中分析的数据结构:

    provs:[{省份1},{省份2},{省份3}....]

    省份定义:{id:xxx, univs:xxx, country_id:xxxx, name:xxxx} univs表示大学

    univs: [{大学1},{大学2},{大学3}....]

    大学定义:{id:xxx, name:xxxx}·  

    利用如下代码进行处理:

     1 // 提取 城市 - 学校 信息
     2 // 省份{id, univs, country_id, name}
     3 // 学校{id, name}
     4 foreach($ret as $k => $v)
     5 {
     6     // $k : order id
     7     // $v : detail array(id, univs, conntry_id, name)
     8     // table: id, cid, sid, name  (city, school, name)
     9     $name = my_decode($v['name']);
    10     create_insert_sql($v['id'], 0, $name);  // $v['univs']表示当前省份下的所有高校
    11     foreach($v['univs'] as $university){
    12         $name = my_decode($university['name']);  // 学校{id, name}
    13         create_insert_sql($university['id'], $v['id'], $name);
    14     }
    15 }

    这里用到了my_decode函数,对形如‘\uaaaa\ubbbb’的字符进行编码处理:

    1 function replace_unicode_escape_sequence($match) {
    2     return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UTF-8');
    3 }
    4 // solution from:http://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha
    5 function my_decode($str)
    6 {
    7     return    $str = preg_replace_callback('/\\\\u([0-9a-f]{4})/i', 'replace_unicode_escape_sequence', $str);
    8 }

    create_insert_sql 函数用来生成进行插入的sql 语句。为了方便,这里直接将sql语句用echo显示在网页上。再将网页文字直接复制到sql 客户端中(笔者使用的navicat)执行。

    1 function create_insert_sql($sid, $cid, $name)
    2 {
    3     $sql =  'insert into ' . TABNAME . '(sid, cid, name) values (' . $sid . ',' . $cid . ',"' . $name . '");';
    4     echo $sql . '<br/>';
    5     return $sql;
    6 }

    获取高校信息的完整代码如下(点击展开):

    View Code
    <?php
    define('TABNAME', 'school');
    
    function x_readfile($filename)
    {
        $handle = fopen($filename, 'r');
        $contents = fread($handle, filesize($filename));
        fclose($handle);
        return $contents;
    }
    function replace_unicode_escape_sequence($match) {
        return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UTF-8');
    }
    // solution from:http://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha
    function my_decode($str)
    {
        return    $str = preg_replace_callback('/\\\\u([0-9a-f]{4})/i', 'replace_unicode_escape_sequence', $str);
    }
    
    function create_insert_sql($sid, $cid, $name)
    {
        $sql =  'insert into ' . TABNAME . '(sid, cid, name) values (' . $sid . ',' . $cid . ',"' . $name . '");';
        echo $sql . '<br/>';
        return $sql;
    }
    
    $content = x_readfile('cu.js');
    // 将文件初始化为标准JSON格式
    $pat = '#(?P<name>\w+):#i';
    $replacement = '"${1}":';
    $str = preg_replace($pat, $replacement, $content);
    $ret = json_decode($str, true);
    // 提取 城市 - 学校 信息
    // 省份{id, univs, country_id, name}
    // 学校{id, name}
    foreach($ret as $k => $v)
    {
        // $k : order id
        // $v : detail array(id, univs, conntry_id, name)
        // table: id, cid, sid, name  (city, school, name)
        $name = my_decode($v['name']);
        create_insert_sql($v['id'], 0, $name);
        foreach($v['univs'] as $university){
            $name = my_decode($university['name']);
            create_insert_sql($university['id'], $v['id'], $name);
        }
    }
    
    ?>

    2. 获得院系信息

    在 1 中已经成功生成了 省份 - 高校 数据。院系信息只需要通过接口,循环访问即可。

    2.1 获取学校ID 集合

    标准的数据库连接操作。利用while循环,将所有sid存储在 $sids 数组中。

     1 // 获取所有学校id
     2 $link = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASS);
     3 if (!$link) {
     4     die('Could not connect: ' . mysql_error());
     5 }
     6 $db_selected = mysql_select_db(DATABASE, $link);
     7 if (!$db_selected) {
     8     die ('Can\'t use : ' . mysql_error());
     9 }
    10 mysql_query("set names utf8;"); 
    11 $sql_statement = 'select sid from school where cid != 0';
    12 $result = mysql_query($sql_statement, $link);
    13 $sids = array();
    14 while($row = mysql_fetch_array($result)){
    15     $sids[] = $row['sid'];
    16 }

    2.2 调用接口,循环访问各高校院系数据

    这里使用到了存储过程进行数据库读写操作,具体分析参看下文的 A) 部分。

     1 // 根据学校 id 去发起请求,一共3163个学校,可见请求之多
     2 $baseurl = 'http://www.renren.com/GetDep.do?id=';
     3 for($i = 0; $i < count($sids); $i++){
     4     $content = x_readfile($baseurl . $sids[$i]);
     5     $pat = '#<option[^>].*?>(.*?)</option>#i';
     6     $matches = array();
     7     preg_match_all($pat, $content, $matches);
     8     // 抛弃第一条结果:<option value=''>院系</option>
     9     array_shift($matches[1]);
    10     try {  
    11         $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    12 
    13         $dbh->beginTransaction();
    14         $dbh->exec('set names utf8;');
    15         foreach($matches[1] as $k => $v){
    16             $dbh->exec(create_insert_sql($sids[$i], $k + 1, mb_convert_encoding($v, 'UTF-8', 'HTML-ENTITIES')));
    17         }
    18         $dbh->commit();
    19 
    20     } catch (Exception $e) {
    21         $dbh->rollBack();
    22         echo "Failed: " . $e->getMessage();
    23     }
    24 }

    因为是访问网络文件,x_readfile有所改变:

    1 function x_readfile($filename)
    2 {
    3     $handle = fopen($filename, 'r');
    4     $contents = stream_get_contents($handle);
    5     return $contents;
    6 }

    没有过于复杂的思路,不过细节上要提到两点。

    A) 使用存储过程写入数据库

    在1.2中生成的sql语句,是直接使用mysql客户端执行的。对于3000多条数据,一条条执行,速度很慢。考虑到院系数据可能是高校数据的10倍,应该用更快的方式写数据库。因此,使用到了存储过程。

    PHP中存储过程的使用可以参看PHP手册,这里给出示范代码:

     1 // 初始化 PDO 存储过程
     2 try {
     3     $dsn = 'mysql:dbname=' . DATABASE . ';host=127.0.0.1';
     4     $dbh = new PDO($dsn, MYSQL_USER, MYSQL_PASS,
     5             array(PDO::ATTR_PERSISTENT => true));
     6 } catch (Exception $e) {
     7     die("Unable to connect: " . $e->getMessage());
     8 }
     9 .......
    10 .......
    11     try {  
    12         $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    13 
    14         $dbh->beginTransaction();
    15         $dbh->exec('set names utf8;');
    16         foreach($matches[1] as $k => $v){
    17             $dbh->exec(create_insert_sql($sids[$i], $k + 1, mb_convert_encoding($v, 'UTF-8', 'HTML-ENTITIES')));
    18         }
    19         $dbh->commit();
    20 
    21     } catch (Exception $e) {
    22         $dbh->rollBack();
    23         echo "Failed: " . $e->getMessage();
    24     }

    B) 数据库乱码

    因为院系信息返回的格式形如”&#xxxxx;&#xxxxx“,进行了十进制编码,需要进行转换。

    mb_convert_encoding($v, 'UTF-8', 'HTML-ENTITIES')

    在插入数据库的时候,发现 department.name字段 全部是乱码,而数据表的format中已将name字段设置为 UTF-8 字符集。经验分析,是在写入数据库时的编码出了问题,因此,在每次写数据库前加一条语句:

    $dbh->exec('set names utf8;');

    这样就没有乱码了。

    获取院系信息的完整代码如下(点击展开):

    View Code
     1 <?php
     2 define('DATABASE', 'test');
     3 define('TABNAME', 'department');
     4 define('MYSQL_HOST', 'localhost');
     5 define('MYSQL_USER', 'root');
     6 define('MYSQL_PASS', 'root');
     7 
     8 function x_readfile($filename)
     9 {
    10     $handle = fopen($filename, 'r');
    11     $contents = stream_get_contents($handle);
    12     return $contents;
    13 }
    14 
    15 function create_insert_sql($sid, $did, $name)
    16 {
    17     $sql =  'insert into ' . TABNAME . '(sid, did, name) values (' . $sid . ',' . $did . ',"' . $name . '");';
    18     //echo $sql . '<br/>';
    19     return $sql;
    20 }
    21 // 获取所有学校id
    22 $link = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASS);
    23 if (!$link) {
    24     die('Could not connect: ' . mysql_error());
    25 }
    26 $db_selected = mysql_select_db(DATABASE, $link);
    27 if (!$db_selected) {
    28     die ('Can\'t use : ' . mysql_error());
    29 }
    30 mysql_query("set names utf8;"); 
    31 $sql_statement = 'select sid from school where cid != 0';
    32 $result = mysql_query($sql_statement, $link);
    33 $sids = array();
    34 while($row = mysql_fetch_array($result)){
    35     $sids[] = $row['sid'];
    36 }
    37 // 初始化 PDO 存储过程
    38 try {
    39     $dsn = 'mysql:dbname=' . DATABASE . ';host=127.0.0.1';
    40     $dbh = new PDO($dsn, MYSQL_USER, MYSQL_PASS,
    41             array(PDO::ATTR_PERSISTENT => true));
    42 } catch (Exception $e) {
    43     die("Unable to connect: " . $e->getMessage());
    44 }
    45 // 根据学校 id 去发起请求,一共3163个学校,可见请求之多
    46 $baseurl = 'http://www.renren.com/GetDep.do?id=';
    47 for($i = 0; $i < count($sids); $i++){
    48     $content = x_readfile($baseurl . $sids[$i]);
    49     $pat = '#<option[^>].*?>(.*?)</option>#i';
    50     $matches = array();
    51     preg_match_all($pat, $content, $matches);
    52     // 抛弃第一条结果:<option value=''>院系</option>
    53     array_shift($matches[1]);
    54     try {  
    55         $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    56 
    57         $dbh->beginTransaction();
    58         $dbh->exec('set names utf8;');
    59         foreach($matches[1] as $k => $v){
    60             $dbh->exec(create_insert_sql($sids[$i], $k + 1, mb_convert_encoding($v, 'UTF-8', 'HTML-ENTITIES')));
    61         }
    62         $dbh->commit();
    63 
    64     } catch (Exception $e) {
    65         $dbh->rollBack();
    66         echo "Failed: " . $e->getMessage();
    67     }
    68 }
    69 ?>

    最后附上两张最终SQL文件结果图:

    一个直接使用这些数据的例子:

    《制作一个选择中国大学的弹框 (数据、步骤、代码)》

    http://www.cnblogs.com/technology/archive/2012/07/30/2607560.html

  • 相关阅读:
    如何使用sendEmail发送邮件
    Linux curl命令详解
    linux比较两个文件是否一样(linux命令md5sum使用方法)
    strace命令用法详解
    strace用法说明
    ORA-12154 TNS无法解析指定的连接标识符
    VNC远程连接阿里云Linux服务器 图形界面
    pycharm配置Git 代码管理
    FireFox浏览器-xpath快速定位插件:Xpath Checker
    odoo 前端模板引擎 Qweb
  • 原文地址:https://www.cnblogs.com/handt/p/2954511.html
Copyright © 2020-2023  润新知