最近用C寫了個爬蟲,純屬練習,離實用還相差甚遠。
bloom.h實現布隆過濾器算法。對一條url拆分為domain和path兩部分,bloomDomain函數判斷domain是否出現過,bloomPath函數判斷path是否出現過。如果domain未出現過,則要先進行DNS解析(解析之后把domain和ip對存入map),再下載網頁;如果domain出現過,則不需要再進行DNS解析,此時如果path也出現過,則該url直接忽略;如果是新的url,需要放入queue。
建立好socket connection后向連接寫入http request,然后把sockfd放入epoll中,同時sockfd設為非阻塞式的。當sockfd準備就緒后,就說明可以從sockfd中讀取http response數據(即下載網頁)了。對于每一個下載網頁的任務創建一個分離的子線程去完成。
下載網頁時,一邊下載,一邊抽取超鏈接放入待爬取的url queue。此時對于每一個下載需要單獨創建一個buffer,比如我把buffer的大小設為1K,則每次從sockfd中read時,最多讀取1K的數據。從buffer中提取出所有超鏈接,然后整個buffer左移,把包含所有超鏈接的最短子串移出去。實際上還可以繼續左移,直到一個空格移到buffer的首位置為止,記下此時buffer中還有多少數據(記為left_size)。則下次從sockfd中read時,需要讀取的數據量為1k - left_size。
每次從buffer中讀取了一些url,去除已下載過的url,再去除已存在于map<domain,ip>中的,剩下的需要進行一次DNS異步解析(使用libevent)。libevent是非線程安全的,即event_base不被多個線程share,所以我每次調用libevent時在一個線程中完成event_base和創建和釋放,即一次完整DNS異步解析是在一個線程中就完成的。
每個線程完成下載網頁的工作后,從url_queue中讀出1個或2個url(epoll中的sockfd比較少時就2個,否則就1個),建立sockte connect,發出http request,然后把sockfd加入到主線程的epoll中去,最后子線程退出。