php实现爬取和分析知乎用户数据


Posted in PHP onJanuary 26, 2016

背景说明:小拽利用php的curl写的爬虫,实验性的爬取了知乎5w用户的基本信息;同时,针对爬取的数据,进行了简单的分析呈现。

php的spider代码和用户dashboard的展现代码,整理后上传github,在个人博客和公众号更新代码库,程序仅供娱乐和学习交流;如果有侵犯知乎相关权益,请尽快联系本人删除。

无图无真相

移动端分析数据截图

php实现爬取和分析知乎用户数据

pc端分析数据截图

php实现爬取和分析知乎用户数据

整个爬取,分析,展现过程大概分如下几步,小拽将分别介绍

  1. curl爬取知乎网页数据
  2. 正则分析知乎网页数据
  3. 数据数据入库和程序部署
  4. 数据分析和呈现

curl爬取网页数据

PHP的curl扩展是PHP支持的,允许你与各种服务器使用各种类型的协议进行连接和通信的库。是一个非常便捷的抓取网页的工具,同时,支持多线程扩展。

本程序抓取的是知乎对外提供用户访问的个人信息页面https://www.zhihu.com/people/xxx,抓取过程需要携带用户cookie才能获取页面。直接上码

获取页面cookie

// 登录知乎,打开个人中心,打开控制台,获取cookie

document.cookie

"_za=67254197-3wwb8d-43f6-94f0-fb0e2d521c31; _ga=GA1.2.2142818188.1433767929; q_c1=78ee1604225d47d08cddd8142a08288b23|1452172601000|1452172601000; _xsrf=15f0639cbe6fb607560c075269064393; cap_id="N2QwMTExNGQ0YTY2NGVddlMGIyNmQ4NjdjOTU0YTM5MmQ=|1453444256|49fdc6b43dc51f702b7d6575451e228f56cdaf5d"; __utmt=1; unlock_ticket="QUJDTWpmM0lsZdd2dYQUFBQVlRSlZUVTNVb1ZaNDVoQXJlblVmWGJ0WGwyaHlDdVdscXdZU1VRPT0=|1453444421|c47a2afde1ff334d416bafb1cc267b41014c9d5f"; __utma=51854390.21428dd18188.1433767929.1453187421.1453444257.3; __utmb=51854390.14.8.1453444425011; __utmc=51854390; __utmz=51854390.1452846679.1.dd1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); __utmv=51854390.100-1|2=registration_date=20150823=1^dd3=entry_date=20150823=1"

抓取个人中心页面

通过curl,携带cookie,先抓取本人中心页面

/**
 * 通过用户名抓取个人中心页面并存储
 * 
 * @param $username str :用户名 flag
 * @return boolean   :成功与否标志
 */
public function spiderUser($username)
{
  $cookie = "xxxx" ;
  $url_info = 'http://www.zhihu.com/people/' . $username; //此处cui-xiao-zhuai代表用户ID,可以直接看url获取本人id
  $ch = curl_init($url_info); //初始化会话
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_COOKIE, $cookie); //设置请求COOKIE
  curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //将curl_exec()获取的信息以文件流的形式返回,而不是直接输出。
  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
  $result = curl_exec($ch);

   file_put_contents('/home/work/zxdata_ch/php/zhihu_spider/file/'.$username.'.html',$result);
   return true;
 }

正则分析网页数据分析新链接,进一步爬取

对于抓取过来的网页进行存储,要想进行进一步的爬取,页面必须包含有可用于进一步爬取用户的链接。通过对知乎页面分析发现:在个人中心页面中有关注人和部分点赞人和被关注人。
如下所示

// 抓取的html页面中发现了新的用户,可用于爬虫

<a class="zm-item-link-avatar avatar-link" href="/people/new-user" data-tip="p$t$new-user">

ok,这样子就可以通过自己-》关注人-》关注人的关注人-》。。。进行不断爬取。接下来就是通过正则匹配提取该信息

// 匹配到抓取页面的所有用户

preg_match_all('/\/people\/([\w-]+)\"/i', $str, $match_arr);

// 去重合并入新的用户数组,用户进一步抓取

self::$newUserArr = array_unique(array_merge($match_arr[1], self::$newUserArr));

到此,整个爬虫过程就可以顺利进行了。
如果需要大量的抓取数据,可以研究下curl_multipcntl进行多线程的快速抓取,此处不做赘述。

分析用户数据,提供分析

通过正则可以进一步匹配出更多的该用户数据,直接上码。

// 获取用户头像
preg_match('/<img.+src=\"?([^\s]+\.(jpg|gif|bmp|bnp|png))\"?.+>/i', $str, $match_img);
$img_url = $match_img[1];

// 匹配用户名:
// <span class="name">崔小拽</span>
preg_match('/<span.+class=\"?name\"?>([\x{4e00}-\x{9fa5}]+).+span>/u', $str, $match_name);
$user_name = $match_name[1];

// 匹配用户简介
// class bio span 中文
preg_match('/<span.+class=\"?bio\"?.+\>([\x{4e00}-\x{9fa5}]+).+span>/u', $str, $match_title);
$user_title = $match_title[1];

// 匹配性别
//<input type="radio" name="gender" value="1" checked="checked" class="male"/> 男  
// gender value1 ;结束 中文
preg_match('/<input.+name=\"?gender\"?.+value=\"?1\"?.+([\x{4e00}-\x{9fa5}]+).+\;/u', $str, $match_sex);
$user_sex = $match_sex[1];

// 匹配地区
//<span class="location item" title="北京">
preg_match('/<span.+class=\"?location.+\"?.+\"([\x{4e00}-\x{9fa5}]+)\">/u', $str, $match_city);
$user_city = $match_city[1];

// 匹配工作
//<span class="employment item" title="人见人骂的公司">人见人骂的公司</span>
preg_match('/<span.+class=\"?employment.+\"?.+\"([\x{4e00}-\x{9fa5}]+)\">/u', $str, $match_employment);
$user_employ = $match_employment[1];

// 匹配职位
// <span class="position item" title="程序猿"><a href="/topic/19590046" title="程序猿" class="topic-link" data-token="19590046" data-topicid="13253">程序猿</a></span>
preg_match('/<span.+class=\"?position.+\"?.+\"([\x{4e00}-\x{9fa5}]+).+\">/u', $str, $match_position);
$user_position = $match_position[1];

// 匹配学历
// <span class="education item" title="研究僧">研究僧</span>
preg_match('/<span.+class=\"?education.+\"?.+\"([\x{4e00}-\x{9fa5}]+)\">/u', $str, $match_education);
$user_education = $match_education[1];

// 工作情况
// <span class="education-extra item" title='挨踢'>挨踢</span>
preg_match('/<span.+class=\"?education-extra.+\"?.+>([\x{4e00}-
\x{9fa5}]+)</u', $str, $match_education_extra);
$user_education_extra = $match_education_extra[1];


// 匹配关注话题数量
// class="zg-link-litblue"><strong>41 个话题</strong></a>
preg_match('/class=\"?zg-link-litblue\"?><strong>(\d+)\s.+strong>/i', $str, $match_topic);
$user_topic = $match_topic[1];

// 关注人数
// <span class="zg-gray-normal">关注了
preg_match_all('/<strong>(\d+)<.+<label>/i', $str, $match_care);
$user_care = $match_care[1][0];
$user_be_careed = $match_care[1][1];

// 历史浏览量
// <span class="zg-gray-normal">个人主页被 <strong>17</strong> 人浏览</span>
preg_match('/class=\"?zg-gray-normal\"?.+>(\d+)<.+span>/i', $str, $match_browse);
$user_browse = $match_browse[1];

在抓取的过程中,有条件的话,一定要通过redis入库,确实能提升抓取和入库效率。没有条件的话只能通过sql优化。这里来几发心德。

数据库表设计索引一定要慎重。在spider爬取的过程中,建议出了用户名,左右字段都不要索引,包括主键都不要,尽可能的提高入库效率,试想5000w的数据,每次添加一个,建立索引需要多少消耗。等抓取完毕,需要分析数据时,批量建立索引。

数据入库和更新操作,一定要批量。 mysql 官方给出的增删改的建议和速度:http://dev.mysql.com/doc/refman/5.7/en/insert-speed.html

# 官方的最优批量插入
INSERT INTO yourtable VALUES (1,2), (5,5), ...;

部署操作。程序在抓取过程中,有可能会出现异常挂掉,为了保证高效稳定,尽可能的写一个定时脚本。每隔一段时间干掉,重新跑,这样即使异常挂掉也不会浪费太多宝贵时间,毕竟,time is money。

#!/bin/bash

# 干掉
ps aux |grep spider |awk '{print $2}'|xargs kill -9
sleep 5s

# 重新跑
nohup /home/cuixiaohuan/lamp/php5/bin/php /home/cuixiaohuan/php/zhihu_spider/spider_new.php &

数据分析呈现

数据的呈现主要使用echarts 3.0,感觉对于移动端兼容还不错。兼容移动端的页面响应式布局主要通过几个简单的css控制,代码如下

// 获取用户头像
preg_match('/<img.+src=\"?([^\s]+\.(jpg|gif|bmp|bnp|png))\"?.+>/i', $str, $match_img);
$img_url = $match_img[1];

// 匹配用户名:
// <span class="name">崔小拽</span>
preg_match('/<span.+class=\"?name\"?>([\x{4e00}-\x{9fa5}]+).+span>/u', $str, $match_name);
$user_name = $match_name[1];

// 匹配用户简介
// class bio span 中文
preg_match('/<span.+class=\"?bio\"?.+\>([\x{4e00}-\x{9fa5}]+).+span>/u', $str, $match_title);
$user_title = $match_title[1];

// 匹配性别
//<input type="radio" name="gender" value="1" checked="checked" class="male"/> 男  
// gender value1 ;结束 中文
preg_match('/<input.+name=\"?gender\"?.+value=\"?1\"?.+([\x{4e00}-\x{9fa5}]+).+\;/u', $str, $match_sex);
$user_sex = $match_sex[1];

// 匹配地区
//<span class="location item" title="北京">
preg_match('/<span.+class=\"?location.+\"?.+\"([\x{4e00}-\x{9fa5}]+)\">/u', $str, $match_city);
$user_city = $match_city[1];

// 匹配工作
//<span class="employment item" title="人见人骂的公司">人见人骂的公司</span>
preg_match('/<span.+class=\"?employment.+\"?.+\"([\x{4e00}-\x{9fa5}]+)\">/u', $str, $match_employment);
$user_employ = $match_employment[1];

// 匹配职位
// <span class="position item" title="程序猿"><a href="/topic/19590046" title="程序猿" class="topic-link" data-token="19590046" data-topicid="13253">程序猿</a></span>
preg_match('/<span.+class=\"?position.+\"?.+\"([\x{4e00}-\x{9fa5}]+).+\">/u', $str, $match_position);
$user_position = $match_position[1];

// 匹配学历
// <span class="education item" title="研究僧">研究僧</span>
preg_match('/<span.+class=\"?education.+\"?.+\"([\x{4e00}-\x{9fa5}]+)\">/u', $str, $match_education);
$user_education = $match_education[1];

// 工作情况
// <span class="education-extra item" title='挨踢'>挨踢</span>
preg_match('/<span.+class=\"?education-extra.+\"?.+>([\x{4e00}-
\x{9fa5}]+)</u', $str, $match_education_extra);
$user_education_extra = $match_education_extra[1];


// 匹配关注话题数量
// class="zg-link-litblue"><strong>41 个话题</strong></a>
preg_match('/class=\"?zg-link-litblue\"?><strong>(\d+)\s.+strong>/i', $str, $match_topic);
$user_topic = $match_topic[1];

// 关注人数
// <span class="zg-gray-normal">关注了
preg_match_all('/<strong>(\d+)<.+<label>/i', $str, $match_care);
$user_care = $match_care[1][0];
$user_be_careed = $match_care[1][1];

// 历史浏览量
// <span class="zg-gray-normal">个人主页被 <strong>17</strong> 人浏览</span>
preg_match('/class=\"?zg-gray-normal\"?.+>(\d+)<.+span>/i', $str, $match_browse);
$user_browse = $match_browse[1];

不足和待学习

整个过程中涉及php,shell,js,css,html,正则等语言和部署等基础知识,但还有诸多需要改进完善,小拽特此记录,后续补充例:

  1. php 采用multicul进行多线程。
  2. 正则匹配进一步优化
  3. 部署和抓取过程采用redis提升存储
  4. 移动端布局的兼容性提升
  5. js的模块化和sass书写css。
PHP 相关文章推荐
在Windows中安装Apache2和PHP4的权威指南
Oct 09 PHP
在字符串中把网址改成超级链接
Oct 09 PHP
?繁体转换的class
Oct 09 PHP
基于curl数据采集之正则处理函数get_matches的使用
Apr 28 PHP
php中sql注入漏洞示例 sql注入漏洞修复
Jan 24 PHP
php ci框架中加载css和js文件失败的解决方法
Mar 03 PHP
php版微信小店调用api示例代码
Nov 12 PHP
PHP简单实现二维数组的矩阵转置操作示例
Nov 24 PHP
PHP实现数据库的增删查改功能及完整代码
Apr 18 PHP
PHP进阶学习之垃圾回收机制详解
Jun 18 PHP
Laravel获取所有的数据库表及结构的方法
Oct 10 PHP
php7性能提升的原因详解
Oct 13 PHP
简单谈谈php延迟静态绑定
Jan 26 #PHP
php制作的简单验证码识别代码
Jan 26 #PHP
php文档工具PHP Documentor安装与使用方法
Jan 25 #PHP
PHP代码维护,重构变困难的4种原因分析
Jan 25 #PHP
PHP+apc+ajax实现的ajax_upload上传进度条代码
Jan 25 #PHP
PHP实现的oracle分页函数实例
Jan 25 #PHP
PHP设置头信息及取得返回头信息的方法
Jan 25 #PHP
You might like
php 无限级数据JSON格式及JS解析
2010/07/17 PHP
getJSON跨域SyntaxError问题分析
2014/08/07 PHP
PHP简单实现文本计数器的方法
2016/04/28 PHP
php解决DOM乱码的方法示例代码
2016/11/20 PHP
解决PHP使用CURL发送GET请求时传递参数的问题
2019/10/11 PHP
JavaScript面向对象程序设计三 原型模式(上)
2011/12/21 Javascript
JavaScript实现网页图片等比例缩放实现代码及调用方式
2013/02/25 Javascript
jquery清空表单数据示例分享
2014/02/13 Javascript
巧用replace将文字表情替换为图片
2014/04/17 Javascript
使用nodejs、Python写的一个简易HTTP静态文件服务器
2014/07/18 NodeJs
使用jquery制作弹出框效果
2015/04/03 Javascript
JavaScript类继承及实例化的方法
2015/07/25 Javascript
jQuery简单注册和禁用全局事件的方法
2016/07/25 Javascript
jQuery File Upload文件上传插件使用详解
2016/12/06 Javascript
js实现把图片的绝对路径转为base64字符串、blob对象再上传
2016/12/29 Javascript
jquery Ajax 全局调用封装实例详解
2017/01/16 Javascript
用React实现一个完整的TodoList的示例代码
2017/10/30 Javascript
基于Bootstrap实现城市三级联动
2017/11/23 Javascript
微信小程序picker组件关于objectArray数据类型的绑定方法
2019/03/13 Javascript
对layui数据表格动态cols(字段)动态变化详解
2019/10/25 Javascript
Vue如何跨组件传递Slot的实现
2020/12/14 Vue.js
Python中单、双下划线的区别总结
2017/12/01 Python
Flask框架配置与调试操作示例
2018/07/23 Python
pygame游戏之旅 添加icon和bgm音效的方法
2018/11/21 Python
python调用opencv实现猫脸检测功能
2019/01/15 Python
Python抓包并解析json爬虫的完整实例代码
2020/11/03 Python
CSS3 :not()选择器实现最后一行li去除某种css样式
2016/10/19 HTML / CSS
VICHY薇姿英国官网:全球专业敏感肌护肤领先品牌
2017/07/04 全球购物
英国电信商店:BT Shop
2019/12/17 全球购物
大学生求职简历的自我评价
2013/10/21 职场文书
杭州西湖英语导游词
2015/02/03 职场文书
公司催款律师函
2015/05/27 职场文书
护士医德医风心得体会
2016/01/25 职场文书
weblogic服务建立数据源连接测试更新mysql驱动包的问题及解决方法
2022/01/22 MySQL
MySQL数据库之内置函数和自定义函数 function
2022/06/16 MySQL
Win11怎么解除儿童账号限制?Win11解除微软儿童账号限制方法
2022/07/07 数码科技