Author: handt

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

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

思路分析:

1. 获得高校数据

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

对该文件进行人工分析,结合页面弹出框,可以得知,该js文件包含了 国家、城市、学校

{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

该接口的返回信息很简单,一个<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 }

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

<?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;');

这样就没有乱码了。

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

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文件结果图:

捕捉 sql server 数据库动作_php

 

捕捉 sql server 数据库动作_数据库_02

 

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

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