[PHP] 又是知乎,用 Beanbun 爬取知乎用戶

最近看了不少關於爬蟲入門的文章,發現其中大部分都是以知乎爲爬取對象,因此此次我也以知乎爲目標來進行爬取的演示,用到的爬蟲框架爲 PHP 編寫的 Beanbun。php

此次寫的內容爲爬取知乎的用戶,下面就是詳細說一下寫爬蟲的過程了。git

爬取知乎用戶的思路比較簡單,就是從某個用戶開始,先抓取這個用戶關注的人和關注他的人,抓取到這些人後,再抓取他們的相關的用戶。
如今知乎是可使用遊客身份進行瀏覽的,也省去了註冊和登陸這一部分。先隨便找個大V吧,由於他們的關注者比較多,我選擇的是大名鼎鼎的張公子,張公子的關注者有13萬,就是說只爬取他的關注者,咱們都能有13萬的用戶數據~github

圖片描述

在用戶頁面中打開chrome瀏覽器的開發者選項,點擊關注者後就能夠看到請求地址和數據。chrome

clipboard.png

就以這個做爲入口開始爬取了~
此處跳過框架的安裝和隊列的開啓,直接上爬蟲的代碼:
(若是須要跳過的部分,能夠看一下文檔數據庫

<?php
use Beanbun\Beanbun;
use Beanbun\Lib\Db;
use GuzzleHttp\Client;

require_once(__DIR__ . '/vendor/autoload.php');

$beanbun = new Beanbun;
$beanbun->name = 'zhihu_user';
$beanbun->count = 5;
$beanbun->interval = 4;
$beanbun->seed = 'https://www.zhihu.com/api/v4/members/zhang-jia-wei/followers?include=data%5B*%5D.answer_count%2Carticles_count%2Cgender%2Cfollower_count%2Cis_followed%2Cis_following%2Cbadge%5B%3F(type%3Dbest_answerer)%5D.topics&offset=0&limit=20';
$beanbun->logFile = __DIR__ . '/zhihu_user_access.log';

上面是對爬蟲的配置,開啓5個進程同時爬取,設置爬取間隔爲4秒。在爬去前,咱們還要設置一下請求的headers,好讓網站認爲是人類再瀏覽他- -。headers的內容從開發者選項中一股腦粘貼進來。json

clipboard.png

$beanbun->beforeDownloadPage = function ($beanbun) {
    // 在爬取前設置請求的 headers 
    $beanbun->options['headers'] = [
        'Host' => 'www.zhihu.com',
        'Connection' => 'keep-alive',
        'Cache-Control' => 'max-age=0',
        'Upgrade-Insecure-Requests' => '1',
        'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36',
        'Accept' => 'application/json, text/plain, */*',
        'Accept-Encoding' => 'gzip, deflate, sdch, br',
        'authorization' => 'oauth c3cef7c66a1843f8b3a9e6a1e3160e20',
    ];
};

而在請求到數據後,我還須要把他們存到數據庫中,由於主要是演示,因此只保存用戶的id,name,follower,following這四個數據。api

// 數據庫配置
Db::$config['zhihu'] = [
    'server' => '127.0.0.1',
    'port' => '3306',
    'username' => 'xxxx',
    'password' => 'xxxx',
    'database_name' => 'zhihu',
    'charset' => 'utf8',
];

$beanbun->afterDownloadPage = function ($beanbun) {
    // 獲取的數據爲 json,先解析
    $data = json_decode($beanbun->page, true);

    // 若是沒有數據或報錯,那多是被屏蔽了。就把地址才從新加回隊列
    if (isset($data['error']) || !isset($data['data'])) {        
        $beanbun->queue()->add($beanbun->url);
        $beanbun->error();
    }
    
    // 若是本次爬取的不是最後一頁,就把下一頁加入隊列
    if ($data['paging']['is_end'] == false) {
        $beanbun->queue()->add($data['paging']['next']);
    }

    $insert = [];
    $date = date('Y-m-d H:i:s');

    foreach ($data['data'] as $user) {
        // 若是關注者或者關注的人小於5個,就不保存了
        if ($user['follower_count'] < 5 || $user['following_count'] < 5) {
            continue ;
        }
        $insert[] = [
            'id' => $user['id'],
            'name' => $user['name'],
            'follower' => $user['follower_count'],
            'following' => $user['following_count'],
            'created_at' => $date,
        ];
        // 把用戶的關注者和關注的人列表加入隊列
        $beanbun->queue()->add('https://www.zhihu.com/api/v4/members/' . $user['url_token'] . '/followers?include=data%5B*%5D.following_count%2Cfollower_count&limit=20&offset=0');
        $beanbun->queue()->add('https://www.zhihu.com/api/v4/members/' . $user['url_token'] . '/followees?include=data%5B*%5D.following_count%2Cfollower_count&limit=20&offset=0');
        }
    }

    if (count($insert)) {
        Db::instance('zhihu')->insert('zhihu_user', $insert);
    }
    // 把剛剛爬取的地址標記爲已經爬取
    $beanbun->queue()->queued($beanbun->queue);
};
// 不須要框架來發現新的網址,
$beanbun->discoverUrl = function () {};
$beanbun->start();

接下來在命令行運行爬蟲瀏覽器

$ php zhihu_user.php start

再去看一眼數據庫,源源不斷的用戶數據保存進來了~
在一切順利的狀況下,我又稍微做了一下死,把爬取的間隔減到了2秒,因而在10幾分鐘以後,我被知乎封掉了....
這種狀況比較常見的解決方式就是使用代理,下面就在原有爬蟲基礎上,增長一個簡單的可定時更新的代理池。
先中止爬蟲app

$ php zhihu_user.php stop

網上有不少免費代理的網站,我隨便選了一個提供免費代理的網站,爬取到代理的數據後,先請求一下163,若是成功的話就加入代理池。

function getProxies($beanbun) {
    $client = new \GuzzleHttp\Client();
    $beanbun->proxies = [];
    $pattern = '/<tr><td>(.+)<\/td><td>(\d+)<\/td>(.+)(HTTP|HTTPS)<\/td><td><div class=\"delay (fast_color|mid_color)(.+)<\/tr>/isU';
    
    for ($i = 1; $i < 5; $i++) {
        $res = $client->get("http://www.mimiip.com/gngao/$i");
        $html = str_replace(['  ', "\r", "\n"], '', $res->getBody());
        preg_match_all($pattern, $html, $match);
        foreach ($match[1] as $k => $v) {
            $proxy = strtolower($match[4][$k]) . "://{$v}:{$match[2][$k]}";
            echo "get proxy $proxy ";
            try {
                $res = $client->get('http://mail.163.com', [
                    'proxy' => $proxy,
                    'timeout' => 6
                ]);
                echo "success.\n";
            } catch (\Exception $e) {
                echo "error.\n";
            }
        }
    }
}

if ($argv[1] == 'start') {
    getProxies($beanbun);
}

$beanbun->startWorker = function($beanbun) {
    // 每隔半小時,更新一下代理池
    Beanbun::timer(1800, 'getProxies', $beanbun);
};

再在以前的 beforeDownloadPage 中加入

if (isset($beanbun->proxies) && count($beanbun->proxies)) {
    $beanbun->options['proxy'] = $beanbun->proxies[array_rand($beanbun->proxies)];
}

再次啓動爬蟲,爬蟲還會接着以前的隊列繼續爬取。

$ php zhihu_user.php start

再看看數據庫,使用代理可能會比以前慢一些,不過數據又繼續增長了。
最終的代碼能夠在 Github 上查看

例子中的代理池仍是比較簡陋的,只是用來講明框架的靈活,而爬取到數據在這裏就不作圖表進行分析了,但願你們關注的也是寫爬蟲的過程。
最後若是你對這篇文章感興趣,但願能到 Github 上給個 star 啦,謝謝~

相關文章
相關標籤/搜索