從人人網抓取高校數據信息,包括,省份 - 高校 - 院系 (提供最終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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM