用PHP爬取个人一卡通的消费记录

技术

来到大学之后,我开始用记账APP来记录我的各种收入和支出,开始用的APP是“口袋记账”,但是我经常遇到在食堂吃完饭忘记自己花了多少钱然后没有及时记帐的情况,学校的一卡通消费查询系统并不是实时统计的,似乎有一两天的延迟,加之碰上期末考试,我也没心思去理那么多了,时间长了遗留下来没记的支出越来越多,所以我打算集中一个时间用Excel来统计一下再把数据导入进去。后来我发现,这破玩意儿居然不支持数据导入?!这么基本的功能都没有,是想圈住用户投资他们的理财产品?怒弃之。换了据说很专业并且支持数据导入的“随手记”。

学校的一卡通消费查询系统不支持数据导出,要我一页页地把数据复制到Excel上?这种重复无聊的事情不应该是靠擅长做这些事情的计算机去解决的吗?所以我开始打算写个脚本一键抓取我的一卡通消费记录。

1765864078.png1765864078.png

首先打开消费记录查询的网页,掏出开发者工具,观察这个网页,找到我们的目标,是一个table标签。

3202413929.png3202413929.png

接着寻找这个标签是怎么生成的,是服务器后端直接生成出来的网页,还是前端ajax访问后端拿到数据再渲染出来的呢?点开Network标签刷新网页,发现网页并没有出现异步的请求,每一页都是一个新的网页,所以是前者的情况。而且每一页的url都是 http://app.scnu.edu.cn/ecard/consump.html?page=[页码] ,所以我只要让程序访问这个url并且解析其中的html,得到表格内部的数据,再通过某种方式收集起来就能完成目标了。

要将结果导入到Excel之中,这里用到了一种简单方便的表格文件格式————CSV,本质上 csv 的表格文件只是一种文本文件,它用逗号等分隔符分隔表格的字段,表格中每一行的数据用换行符分隔(在Excel里面换行符是"rn")

字段1,字段2,字段3,字段4
A,B,C,D
1,2,3,4

就是这么简洁明了!对于任意的程序,只需要简单的字符串拼接即可生成一个csv格式的表格。

经过我的测试,我这一个学期以来的消费记录在这个网页上只有50多页,所以爬虫需要爬取的数据量很小,处理起来是完全没有压力的,直接一次性得到所有的结果之后保存文件就行了。

至于爬虫程序的语言选择,我也没什么好说的,目前我也就对PHP比较熟悉一些,所以接下来的程序我也是用PHP完成的。

首先确定我应该怎么模拟登录这个系统,这里我们应该要知道,HTTP是一种无状态的协议,所以服务器要确定当前请求的用户是谁的话,就要通过HTTP请求的Cookie中保存的信息来确定。所以我们如果要让服务器知道爬虫发出的HTTP请求的用户是我的话,应该让爬虫发出的HTTP请求带上这个cookie,在这里我们可以把这个cookie从chrome复制出来,把它的值保存在某个变量之中备用。

查看浏览器访问这个页面的header,发现cookie只有JSESSIONID。

1147968629.png1147968629.png

接下来写一个循环,把每一页抓取的结果添加到保存结果的字符串之中,当找不到数据时则跳出循环,保存结果,程序结束。

提取数据时我用了 simple_html_dom ,一个简单方便的解析html中的DOM结构的库。

最后将字符串中的内容保存到 result.csv 中。

代码如下:(GitHub地址: scnu_ecard_spider

<?php
/**
 * Created by PhpStorm.
 * User: qing
 * Date: 17-2-12
 * Time: 下午1:59
 */
require_once "simple_html_dom.php";

$cookie = "JSESSIONID=C73FF91A6FF439C073EC664E532C67E6";
$result = "消费时间,消费金额,卡内余额,消费地点\r\n";

$page = 1;

while (true){
    echo "Fetching page $page...\n";
    $html = get_url("http://app.scnu.edu.cn/ecard/consump.html?page=$page", $cookie);
    //echo $html;exit();
    if (!$html)
        exit("Network Error!!");
    if (strpos($html, "暂无数据") !== false)
        break;
    $dom = new simple_html_dom();
    $dom->load($html);
    $trs = $dom->find("table", 0)->find("tr");
    foreach ($trs as $tr){
        $arr = [];
        $tds = $tr->find("td");
        //忽略表头
        if (!$tds)
            continue;
        foreach ($tds as $td){
            //过滤结果中的标签
            $arr[] = strip_tags($td->innertext);
        }
        $result .= implode(",", $arr) . "\r\n";
    }
    $page++;
}

file_put_contents("result.csv", $result);

echo "Finished\n";

function get_url($url, $cookie=''){
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('User-Agent: Mozilla/5.0 (Linux; U; Android 4.1.2; zh-cn; MB526 Build/JZO54K) AppleWebKit/530.17 (KHTML, like Gecko) FlyFlow/2.4 Version/4.0 Mobile Safari/530.17 baidubrowser/042_1.8.4.2_diordna_458_084/alorotoM_61_2.1.4_625BM/1200a/39668C8F77034455D4DED02169F3F7C7%7C132773740707453/1','Referer: http://app.scnu.edu.cn/ecard/consump.html'));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_COOKIE, $cookie);
    $result = curl_exec($ch);
    $httpCode = curl_getinfo($ch,CURLINFO_HTTP_CODE);
    if ($httpCode != 200) return false;
    curl_close($ch);
    return $result;
}

运行结果:

3392441289.png3392441289.png

4014514587.png4014514587.png

实践证明,cli模式下运行的PHP还是很给力的^_^