Hyperf抓取網易雲音樂評論

zz_lkw發表於2023-04-17
<?php

namespace App\Service\Song;

use Hyperf\DbConnection\Db;
use \Hyperf\Utils\WaitGroup;

class SongService
{
    //獲取評論的請求地址
    private $musicPostUrl = 'https://music.163.com/weapi/v1/resource/comments/R_SO_4_';

    /**
     * todo 【注意】:$musicPage * $musicLimit > 1000的話,介面會請求不了資料,具體原因不詳
     */
    //獲取評論分頁的頁數(目前好像最多請求50頁數,超過會請求錯誤)
    private $musicPage = 50;
    //獲取評論分頁的條數
    private $musicLimit = 20;

    /**
     * 獲取評論
     * @param $data
     */
    public function getComment($data = [])
    {
        //歌曲名稱、歌曲ID
        $default = [
            'text' => '帶我去找夜生活',
            'songId' => '1410647903'
        ];
        $data    = (!empty($data) ? $data : $default);

        // 設定請求頭
        $headers = array(
            'Accept:*/*',
            'Accept-Language:zh-CN,zh;q=0.9',
            'Connection:keep-alive',
            'Content-Type:application/x-www-form-urlencoded',
            'Host:music.163.com',
            'Origin:https://music.163.com',
            // 模擬瀏覽器設定 User-Agent ,否則取到的資料不完整
            'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
        );
        // 拼接歌曲的url
        $url = $this->musicPostUrl . $data['songId'] . '?csrf_token=';
        // 拼接加密 params 用到的第一個引數
        $songName = $data['text'];
        //利用協程去實現
        $wg = new WaitGroup();
        $wg->add($this->musicPage); //100併發
        for ($j = 0; $j < $this->musicPage; $j++) {
            $limit       = $this->musicLimit;
            $offset      = $j * $limit;
            $first_param = '{"rid":"R_SO_4_' . $data['songId'] . '","offset":' . $offset . ',"total":"true","limit":' . $limit . ',"csrf_token":""}';
            // 記錄評論
            try {
                co(function () use ($wg, $j, $first_param, $url, $headers, $songName) {
                    $params     = array('params' => $this->aesGetParams($first_param), 'encSecKey' => $this->getEncSecKey());
                    $htmlInfo   = $this->httpPost($url, $headers, http_build_query($params));
                    $insertData = $this->saveComment(json_decode($htmlInfo, true), $songName);
                    //var_dump($insertData);
                    var_dump("當前迴圈第:{$j} 次");
                    //此處為資料庫入庫:
                    if ($insertData) {
                        Db::table('song')->insert($insertData);
                    }
                    $wg->done();
                });
            } catch (\Exception $exception) {
                var_dump($exception->getMessage());
            }
            // 沒有設定代理IP,間隔2秒執行(不然網易雲爬取不了) ,如需要代理(自行百度代理IP),下面sleep可以不用寫
            sleep(2);
        }
        $wg->wait();
    }

    /**
     *  加密獲取params
     * @param $param // 待加密的明文資訊資料
     * @param string $method // 加密演算法
     * @param string $key // key
     * @param string $options // options 是以下標記的按位或: OPENSSL_RAW_DATAOPENSSL_ZERO_PADDING
     * @param string $iv //NULL 的初始化向量
     * @return string
     *
     * $key 在加密 params 中第一次用的是固定的第四個引數 0CoJUm6Qyw8W8jud,在第二次加密中用的是 js 中隨機生成的16位字串
     */
    private function aesGetParams($param, $method = 'AES-128-CBC', $key = 'JK1M5sQAEcAZ46af', $options = '0', $iv = '0102030405060708')
    {
        $firstEncrypt  = openssl_encrypt($param, $method, '0CoJUm6Qyw8W8jud', $options, $iv);
        $secondEncrypt = openssl_encrypt($firstEncrypt, $method, $key, $options, $iv);
        return $secondEncrypt;
    }

    /**
     *  encSecKey 在 js 中有 res 方法加密。
     *  其中三個引數分別為上面隨機生成的16為字串,第二個引數 $second_param,第三個引數 $third_param 都是固定寫死的,這邊使用抄下來的一個固定 encSecKey
     * @return bool
     */
    private function getEncSecKey()
    {
        $getEncSecKey = '2a98b8ea60e8e0dd0369632b14574cf8d4b7a606349669b2609509978e1b5f96ed8fbe53a90c0bb74497cd2eb965508bff5bfa065394a52ea362539444f18f423f46aded5ed9a1788d110875fb976386aa4f5d784321433549434bccea5f08d1888995bdd2eb015b2236f5af15099e3afbb05aa817c92bfe3214671e818ea16b';
        return $getEncSecKey;
    }

    /**
     * curl 傳送 post 請求
     * @param $url
     * @param $header
     * @param $data
     * @return mixed
     */
    public function httpPost($url, $header, $data)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, 0);                // 0不帶標頭檔案,1帶標頭檔案(返回值中帶有標頭檔案)
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);        // 對認證證照來源的檢查
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);        // 使用自動跳轉
        curl_setopt($ch, CURLOPT_AUTOREFERER, 1);           // 自動設定Referer
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);        //設定等待時間
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);              //設定cURL允許執行的最長秒數
        $content = curl_exec($ch);
        curl_close($ch);
        return $content;
    }

    /**
     * 獲取評論內容
     * @param $songName
     * @param $data
     */
    public function saveComment($data, $songName)
    {
        if (!($data['comments'] ?? [])) {
            return false;
        }
        foreach ($data['comments'] as $v) {
            $insertData[] = [
                'user_id' => $v['user']['userId'] ?? 0,
                'song_name' => $songName ?? '未知歌曲',
                'avatar_url' => $v['user']['avatarUrl'] ?? '',
                'parent_comment_id' => $v['parentCommentId'] ?? 0,
                'comment_id' => $v['commentId'] ?? 0,
                'nickname' => $v['user']['nickname'] ?? '',
                'content' => $v['content'] ?? '',
                'like_count' => $v['likedCount'] ?? 0,
                'ip' => $v['ipLocation']['location'] ?? '未知',
                'create_time' => date("Y-m-d H:i:s", $v['time'] / 1000),
            ];
        }
        return $insertData;
    }

}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章