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 }
获取高校信息的完整代码如下(点击展开):

<?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文件结果图:
一个直接使用这些数据的例子:
《制作一个选择中国大学的弹框 (数据、步骤、代码)》
http://www.cnblogs.com/technology/archive/2012/07/30/2607560.html