搜尋
首頁後端開發php教程PHP爬蟲之百萬等級知乎用戶資料爬取與分析

PHP爬蟲之百萬等級知乎用戶資料爬取與分析

Apr 20, 2018 am 11:58 AM
php數據使用者

這篇文章介紹的內容是關於PHP爬蟲之百萬級別知乎用戶數據爬取與分析,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下



這篇文章主要介紹了PHP百萬等級知乎使用者資料爬取與分析的相關資料,需要的朋友可以參考下方

開發前的準備

  • 安裝Linux系統(Ubuntu14 .04),在VMWare虛擬機器下方安裝一個Ubuntu;

  • 安裝PHP5.6或以上版本;



##安裝MySQL5.5或以上版本;


# #安裝curl、pcntl擴充。



###使用PHP的curl擴充抓取頁面資料#########PHP的curl擴充功能是PHP支援的允許你與各種伺服器使用各種類型的協定進行連接和通訊的庫。 ######本程式是抓取知乎的使用者數據,要能存取使用者個人頁面,需要使用者登入後的才能存取。當我們在瀏覽器的頁面中點擊一個用戶頭像鏈接進入用戶個人中心頁面的時候,之所以能夠看到用戶的信息,是因為在點擊鏈接的時候,瀏覽器幫你將本地的cookie帶上一齊提交到新的頁面,所以你就能進入到使用者的個人中心頁面。因此實現訪問個人頁面之前需要先獲得用戶的cookie訊息,然後在每次curl請求的時候帶上cookie資訊。在取得cookie資訊方面,我使用了自己的cookie,在頁面中可以看到自己的cookie資訊:######################## ##一個個複製,以”__utma=?;__utmb=?;”這樣的形式組成一個cookie字串。接下來就可以使用該cookie字串來傳送請求。 ######初始的範例:###############################

? 4

56
7

8

9

#
$url = 'http://www.zhihu.com/people/mora-hu/about'; //此处mora-hu代表用户ID
$ch = curl_init($url); //初始化会话
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_COOKIE, $this->config_arr['user_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);
return $result; //抓取的结果


##

運行上面的程式碼可以獲得mora-hu用戶的個人中心頁面。利用此結果再使用正規表示式對頁面進行處理,就能取得到姓名,性別等所需抓取的資訊。

1、圖片防盜鏈

在對回傳結果進行正規處理後輸出個人資訊的時候,發現在頁面中輸出使用者頭像時無法打開。經過查閱資料得知,是因為知乎對圖片做了防盜鏈處理。解決方案就是請求圖片的時候在請求頭裡偽造一個referer。

在使用正規表示式取得到圖片的連結之後,再發一次請求,這時候帶上圖片請求的來源,說明該請求來自知乎網站的轉發。具體範例如下:




?

1

2

3

4

5

6

7

8

#9

10

##11

12

13

14

15

#16

17

18

# #19

20

21

22


function getImg($url, $u_id)
{
  if (file_exists('./images/' . $u_id . ".jpg"))
  {
    return "images/$u_id" . '.jpg';
  }
  if (empty($url))
  {
    return '';
  }
  $context_options = array( 
    'http' => 
    array(
      'header' => "Referer:http://www.zhihu.com"//带上referer参数
    )
  );
 
  $context = stream_context_create($context_options); 
  $img = file_get_contents('http:' . $url, FALSE, $context);
  file_put_contents('./images/' . $u_id . ".jpg", $img);
  return "images/$u_id" . '.jpg';
}


2、爬取更多用戶

抓取了自己的個人資訊後,就需要再存取用戶的追蹤者和追蹤了的用戶列表獲取更多的用戶資訊。然後一層一層地訪問。可以看到,在個人中心頁面裡,有兩個鏈接如下:

這裡有兩個鏈接,一個是關注了,另一個是關注者,以“關注了」的連結為例。用正規匹配去匹配到相應的鏈接,得到url之後用curl帶上cookie再發一次請求。抓取到使用者追蹤了的用於列表頁之後,可以得到下面的頁面:



分析頁面的html結構,因為只要得到用戶的訊息,所以只需要框住的這一塊的p內容,用戶名就在這裡面。可以看到,用戶追蹤了的頁面的url是:

不同的用戶的這個url幾乎是一樣的,不同的地方就在於用戶名那裡。用正規匹配拿到用戶名列表,一個一個拼url,然後再逐個發請求(當然,一個一個是比較慢的,下面有解決方案,這個稍後會說到)。進入到新用戶的頁面之後,再重複上面的步驟,就這樣不斷循環,直到達到你所要的資料量。

3、Linux統計檔案數量

#腳本跑了一段時間後,需要看看究竟取得了多少圖片,當資料量比較大的時候,打開資料夾查看圖片數量就有點慢。腳本是在Linux環境下運行的,因此可以使用Linux的命令來統計檔案數量:




###################################### ######?######

1


ls-l | grep"^-"wc -l


其中, ls -l 是長列表輸出該目錄下的檔案資訊(這裡的檔案可以是目錄、連結、裝置檔案等); grep "^-" 過濾長清單輸出訊息, "^-" 只保留一般文件,如果只保留目錄是"^d" ; wc -l 是統計輸出資訊的行數。以下是一個運行範例:

4、插入MySQL時重複資料的處理

程式運行了一段時間後,發現有很多使用者的資料是重複的,因此需要在插入重複使用者資料的時候做處理。處理方案如下:

1)插入資料庫之前檢查資料是否已經存在資料庫;

2)新增唯一索引,插入時使用INSERT INTO ... ON DUPLICATE KEY UPDATE...

3)新增唯一索引,插入時使用INSERT INGNORE INTO...

4)新增唯一索引,插入時使用REPLACE INTO...

第一種方案是最簡單但也是效率最差的方案,因此不採取。二和四方案的執行結果是一樣的,不同的是,在遇到相同的資料時, INSERT INTO … ON DUPLICATE KEY UPDATE 是直接更新的,而REPLACE INTO 是先刪除舊的資料然後插入新的,在這個過程中,還需要重新維護索引,所以速度慢。所以在二和四兩者間選擇了第二種方案。而第三種方案, INSERT INGNORE 會忽略執行INSERT語句出現的錯誤,不會忽略語法問題,但忽略主鍵存在的情況。這樣一來,使用 INSERT INGNORE 就更好了。最終,考慮到要在資料庫中記錄重複資料的條數,因此在程式中採用了第二種方案。

5、使用curl_multi實現多線程抓取頁面

剛開始單進程而且單一curl去抓取數據,速度很慢,掛機爬了一個晚上只能抓到2W的數據,於是便想到能不能在進入新的用戶頁面發curl請求的時候一次性請求多個用戶,後來發現了curl_multi這個好東西。 curl_multi這類函數可以實作同時請求多個url,而不是一個個請求,這類似於linux系統中一個行程開多條執行緒執行的功能。以下是使用curl_multi實作多執行緒爬蟲的範例:




?

1

2

3

4

5

6

7

8

#9

10

##11

12

13

14

15

#16

17

18

# #19

20

21

22

23

24

25

26

27

28

29

30

31

#32

33

34

35

#36

37

#38

###################################################### #第39章###

40

41

42

43

44

45

46

#47

48

49

50

#51

#52

#53

############################################################# #第54章###


$mh = curl_multi_init(); //返回一个新cURL批处理句柄
for ($i = 0; $i < $max_size; $i++)
{
  $ch = curl_init(); //初始化单个cURL会话
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_URL, &#39;http://www.zhihu.com/people/&#39; . $user_list[$i] . &#39;/about&#39;);
  curl_setopt($ch, CURLOPT_COOKIE, self::$user_cookie);
  curl_setopt($ch, CURLOPT_USERAGENT, &#39;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36&#39;);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
  $requestMap[$i] = $ch;
  curl_multi_add_handle($mh, $ch); //向curl批处理会话中添加单独的curl句柄
}
 
$user_arr = array();
do {
        //运行当前 cURL 句柄的子连接
  while (($cme = curl_multi_exec($mh, $active)) == CURLM_CALL_MULTI_PERFORM);
 
  if ($cme != CURLM_OK) {break;}
        //获取当前解析的cURL的相关传输信息
  while ($done = curl_multi_info_read($mh))
  {
    $info = curl_getinfo($done[&#39;handle&#39;]);
    $tmp_result = curl_multi_getcontent($done[&#39;handle&#39;]);
    $error = curl_error($done[&#39;handle&#39;]);
 
    $user_arr[] = array_values(getUserInfo($tmp_result));
 
    //保证同时有$max_size个请求在处理
    if ($i < sizeof($user_list) && isset($user_list[$i]) && $i < count($user_list))
    {
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_HEADER, 0);
      curl_setopt($ch, CURLOPT_URL, &#39;http://www.zhihu.com/people/&#39; . $user_list[$i] . &#39;/about&#39;);
      curl_setopt($ch, CURLOPT_COOKIE, self::$user_cookie);
      curl_setopt($ch, CURLOPT_USERAGENT, &#39;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36&#39;);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
      $requestMap[$i] = $ch;
      curl_multi_add_handle($mh, $ch);
 
      $i++;
    }
 
    curl_multi_remove_handle($mh, $done[&#39;handle&#39;]);
  }
 
  if ($active)
    curl_multi_select($mh, 10);
} while ($active);
 
curl_multi_close($mh);
return $user_arr;


6、HTTP 429 Too Many Requests

使用curl_multi函數可以同時發多個請求,但是在執行過程中使同時發200個請求的時候,發現很多請求無法回傳了,也就是發現了丟包的情況。進一步分析,使用 curl_getinfo 函數列印每個請求句柄訊息,函數傳回一個包含HTTP response訊息的關聯數組,其中有一個欄位是http_code,表示請求傳回的HTTP狀態碼。看到有很多個請求的http_code都是429,這個回傳碼的意思是發送太多請求了。我猜是知乎做了防爬蟲的防護,於是我就拿其他的網站來做測試,發現一次性發200個請求時沒問題的,證明了我的猜測,知乎在這方面做了防護,即一次性的請求數量是有限制的。於是我不斷地減少請求數量,發現在5的時候就沒有丟包情況了。說明在這個程式裡一次最多只能發5個請求,雖然不多,但這也是一次小提升了。

7、使用Redis保存已經造訪過的使用者

#抓取使用者的過程中,發現有些使用者是已經造訪過的,而且他的追蹤者和追蹤了的用戶都已經取得過了,雖然在資料庫的層面做了重複資料的處理,但是程式還是會使用curl發請求,這樣重複的發送請求就有很多重複的網路開銷。還有一個就是待抓取的用戶需要暫時保存在一個地方以便下一次執行,剛開始是放到數組裡面,後來發現要在程式裡添加多進程,在多進程編程裡,子進程會共享程序代碼、函式庫,但是進程使用的變數與其他行程所使用的截然不同。不同進程之間的變數是分離的,不能被其他行程讀取,所以是不能使用陣列的。因此就想到了使用Redis快取來保存已經處理好的使用者以及待抓取的使用者。這樣每次執行完的時候都把使用者push到一個already_request_queue佇列中,把待抓取的使用者(即每個使用者的追蹤者和追蹤了的使用者清單)push到request_queue裡面,然後每次執行前都從request_queue裡pop一個用戶,然後判斷是否在already_request_queue裡面,如果在,則進行下一個,否則就繼續執行。

在PHP中使用redis範例:




?

1

2

3

4

5

6

7

8


<?php
  $redis = new Redis();
  $redis->connect(&#39;127.0.0.1&#39;, &#39;6379&#39;);
  $redis->set(&#39;tmp&#39;, &#39;value&#39;);
  if ($redis->exists(&#39;tmp&#39;))
  {
    echo $redis->get(&#39;tmp&#39;) . "\n";
  }


8、使用PHP的pcntl擴充實作多進程

#改用了curl_multi函數實作多執行緒抓取使用者資訊之後,程式運行了一個晚上,最後得到的數據有10W。還不能達到自己的理想目標,於是就繼續優化,後來發現php裡面有一個pcntl擴充可以實現多進程程式設計。以下是多程式設計程式設計的範例:




##1

2

3

##4

##5

#6######7###

8

#9

10

##11

12

13

14

15

#16

17

18

# #19

20

//PHP多进程demo
//fork10个进程
for ($i = 0; $i < 10; $i++) {
  $pid = pcntl_fork();
  if ($pid == -1) {
    echo "Could not fork!\n";
    exit(1);
  }
  if (!$pid) {
    echo "child process $i running\n";
    //子进程执行完毕之后就退出,以免继续fork出新的子进程
    exit($i);
  }
}
 
//等待子进程执行完毕,避免出现僵尸进程
while (pcntl_waitpid(0, $status) != -1) {
  $status = pcntl_wexitstatus($status);
  echo "Child $status completed\n";
}


9、在Linux下查看系统的cpu信息

实现了多进程编程之后,就想着多开几条进程不断地抓取用户的数据,后来开了8调进程跑了一个晚上后发现只能拿到20W的数据,没有多大的提升。于是查阅资料发现,根据系统优化的CPU性能调优,程序的最大进程数不能随便给的,要根据CPU的核数和来给,最大进程数最好是cpu核数的2倍。因此需要查看cpu的信息来看看cpu的核数。在Linux下查看cpu的信息的命令:




?

1

cat /proc/cpuinfo


結果如下:

其中,model name表示cpu型別訊息,cpu cores表示cpu核數。這裡的核數是1,因為是在虛擬機器下運行,分配到的cpu核數比較少,因此只能開2個進程。最終的結果是,花了一個週末就抓取了110萬的用戶資料。

10、多進程程式設計中Redis和MySQL連線問題

在多進程條件下,程式運行了一段時間後,發現數據不能插入到資料庫,會報mysql too many connections的錯誤,redis也是如此。

下面這段程式碼會執行失敗:




# ?

1

2

3

4

5

6

7

8

9

10

11

12

13


<?php
   for ($i = 0; $i < 10; $i++) {
     $pid = pcntl_fork();
     if ($pid == -1) {
        echo "Could not fork!\n";
        exit(1);
     }
     if (!$pid) {
        $redis = PRedis::getInstance();
        // do something   
        exit;
     }
   }


根本原因是在各個子進程創建時,就已經繼承了父進程一份完全一樣的拷貝。物件可以拷貝,但是已創建的連接不能被拷貝成多個,由此產生的結果,就是各個進程都使用同一個redis連接,各幹各的事,最終產生莫名其妙的衝突。

解決方法: >程式無法完全保證在fork進程之前,父進程不會建立redis連線實例。因此,要解決這個問題只能靠子進程本身了。試想一下,如果在子進程中取得的實例只與目前進程相關,那麼這個問題就不存在了。於是解決方案就是稍微改造一下redis類別實例化的靜態方式,與目前進程ID綁定。

改造後的程式碼如下:




?

1

2

#3

4

#5

6

7

8

9

10


<?php
   public static function getInstance() {
     static $instances = array();
     $key = getmypid();//获取当前进程ID
     if ($empty($instances[$key])) {
        $inctances[$key] = new self();
     }
 
     return $instances[$key];
   }


11、PHP統計腳本執行時間

因為想知道每個行程花費的時間是多少,因此寫個函數統計腳本執行時間:




#?

1

2

3

4

5

6######7 ###

8

9

10

11

12

13

14

15

16

17


function microtime_float()
{
   list($u_sec, $sec) = explode(&#39; &#39;, microtime());
   return (floatval($u_sec) + floatval($sec));
}
 
$start_time = microtime_float();
 
//do something
usleep(100);
 
$end_time = microtime_float();
$total_time = $end_time - $start_time;
 
$time_cost = sprintf("%.10f", $total_time);
 
echo "program cost total " . $time_cost . "s\n";


以上就是本文的全部內容,供大家參考,希望對大家的學習有所幫助。

相關推薦:

利用php爬蟲分析南京房價


以上是PHP爬蟲之百萬等級知乎用戶資料爬取與分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
超越炒作:評估當今PHP的角色超越炒作:評估當今PHP的角色Apr 12, 2025 am 12:17 AM

PHP在現代編程中仍然是一個強大且廣泛使用的工具,尤其在web開發領域。 1)PHP易用且與數據庫集成無縫,是許多開發者的首選。 2)它支持動態內容生成和麵向對象編程,適合快速創建和維護網站。 3)PHP的性能可以通過緩存和優化數據庫查詢來提升,其廣泛的社區和豐富生態系統使其在當今技術棧中仍具重要地位。

PHP中的弱參考是什麼?什麼時候有用?PHP中的弱參考是什麼?什麼時候有用?Apr 12, 2025 am 12:13 AM

在PHP中,弱引用是通過WeakReference類實現的,不會阻止垃圾回收器回收對象。弱引用適用於緩存系統和事件監聽器等場景,需注意其不能保證對象存活,且垃圾回收可能延遲。

解釋PHP中的__ Invoke Magic方法。解釋PHP中的__ Invoke Magic方法。Apr 12, 2025 am 12:07 AM

\_\_invoke方法允許對象像函數一樣被調用。 1.定義\_\_invoke方法使對象可被調用。 2.使用$obj(...)語法時,PHP會執行\_\_invoke方法。 3.適用於日誌記錄和計算器等場景,提高代碼靈活性和可讀性。

解釋PHP 8.1中的纖維以進行並發。解釋PHP 8.1中的纖維以進行並發。Apr 12, 2025 am 12:05 AM

Fibers在PHP8.1中引入,提升了並發處理能力。 1)Fibers是一種輕量級的並發模型,類似於協程。 2)它們允許開發者手動控制任務的執行流,適合處理I/O密集型任務。 3)使用Fibers可以編寫更高效、響應性更強的代碼。

PHP社區:資源,支持和發展PHP社區:資源,支持和發展Apr 12, 2025 am 12:04 AM

PHP社區提供了豐富的資源和支持,幫助開發者成長。 1)資源包括官方文檔、教程、博客和開源項目如Laravel和Symfony。 2)支持可以通過StackOverflow、Reddit和Slack頻道獲得。 3)開發動態可以通過關注RFC了解。 4)融入社區可以通過積極參與、貢獻代碼和學習分享來實現。

PHP與Python:了解差異PHP與Python:了解差異Apr 11, 2025 am 12:15 AM

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

php:死亡還是簡單地適應?php:死亡還是簡單地適應?Apr 11, 2025 am 12:13 AM

PHP不是在消亡,而是在不斷適應和進化。 1)PHP從1994年起經歷多次版本迭代,適應新技術趨勢。 2)目前廣泛應用於電子商務、內容管理系統等領域。 3)PHP8引入JIT編譯器等功能,提升性能和現代化。 4)使用OPcache和遵循PSR-12標準可優化性能和代碼質量。

PHP的未來:改編和創新PHP的未來:改編和創新Apr 11, 2025 am 12:01 AM

PHP的未來將通過適應新技術趨勢和引入創新特性來實現:1)適應云計算、容器化和微服務架構,支持Docker和Kubernetes;2)引入JIT編譯器和枚舉類型,提升性能和數據處理效率;3)持續優化性能和推廣最佳實踐。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版