网络爬虫初步:从一个入口链接开始不断抓取页面中的网址并入库
前言:
? 在上一篇《網(wǎng)絡(luò)爬蟲初步:從訪問網(wǎng)頁到數(shù)據(jù)解析》中,我們討論了如何爬取網(wǎng)頁,對爬取的網(wǎng)頁進行解析,以及訪問被拒絕的網(wǎng)站。在這一篇博客中,我們可以來了解一下拿到解析的數(shù)據(jù)可以做的事件。在這篇博客中,我主要是說明要做的兩件事,一是入庫,二是遍歷拿到的鏈接繼續(xù)訪問。如此往復(fù),這樣就構(gòu)成了一個網(wǎng)絡(luò)爬蟲的雛形。
筆者環(huán)境:
? 系統(tǒng):???? Windows 7
??????????? CentOS 6.5
? 運行環(huán)境:? JDK 1.7
??????????? Python 2.6.6
? IDE:????? Eclipse Release 4.2.0
??????????? PyCharm 4.5.1
? 數(shù)據(jù)庫:??? MySQL Ver 14.14 Distrib 5.1.73
效果圖:
? 這里只截取開始的一部分?jǐn)?shù)據(jù)。這些數(shù)據(jù)我是保存在MySQL中的。
?
?
思路梳理:
? 前面說到,我們拿到數(shù)據(jù)要做兩件:數(shù)據(jù)保存與數(shù)據(jù)分析。
? 我們整個邏輯過程是這樣的:
??? 1.Java傳遞鏈接參數(shù)給Python;
??? 2.Python解析HTML返回必要信息給Java;
??? 3.Java拿到數(shù)據(jù)進行入庫;
??? 4.對解析出的有效鏈接進行繼續(xù)遍歷(這里是采用圖的廣度優(yōu)先遍歷)
??? 5.反復(fù)以上的4個步驟,直到?jīng)]有可繼續(xù)訪問的有效鏈接為止,這里是使用遞歸迭代。
? 關(guān)于數(shù)據(jù)保存,倒是沒有什么好說的。因為我是在Linux(CentOS)下運行程序的。所以,你的Linux中必須要有MySQL,另外,我是通過Java來進行數(shù)據(jù)庫操作的,所以這里你的系統(tǒng)中也有要Mysql的Java驅(qū)動包。
?
開發(fā)過程:
1.Python解析數(shù)據(jù)
get_html_response.py
html_parser.py
# encoding=utf-8'''對Html文件進行解析 '''import sys reload(sys) sys.setdefaultencoding('utf8')from list_web_parser import ListWebParser import get_html_response as gethdef main(html):myp = ListWebParser()get_html = geth.get_html_response(html)myp.feed(get_html)link_list = myp.getLinkList()myp.close()for item in link_list:if item[0] and item[1]:print item[0], '$#$', item[1]if __name__ == "__main__":if not sys.argv or len(sys.argv) < 2:print 'You leak some arg.' # http://www.cnblogs.com/Stone-sqrt3/main(sys.argv[1])
2.Java入庫
對于Java中對數(shù)據(jù)庫的操作,也沒什么好說說明的。如果你寫地JDBC,那么這對于你而言將是小菜一碟。關(guān)鍵代碼如下:
public class DBServer {private String mUrl = DBModel.getMysqlUrl();private String mUser = DBModel.getMysqlUesr();private String mPassword = DBModel.getMysqlPassword();private String mDriver = DBModel.getMysqlDerver();private Connection mConn = null;private Statement mStatement = null;public DBServer() {initEvent();}private void initEvent() {mUrl = DBModel.getMysqlUrl();mUser = DBModel.getMysqlUesr();mPassword = DBModel.getMysqlPassword();mDriver = DBModel.getMysqlDerver();try {Class.forName(mDriver).newInstance();mConn = DriverManager.getConnection(mUrl, mUser, mPassword);mStatement = mConn.createStatement();} catch (Exception e) {e.printStackTrace();}}/*** 數(shù)據(jù)庫查詢* TODO* DBServer* @param sql* 查詢的sql語句*/public void select(String sql) {try {ResultSet rs = mStatement.executeQuery(sql);while (rs.next()) {String name = rs.getString("name");System.out.println(name);}rs.close();} catch (SQLException e) {e.printStackTrace();}}/*** 插入新數(shù)據(jù)* DBServer* @param sql* 插入的sql語句*/public int insert(String sql) {try {int raw = mStatement.executeUpdate(sql);return raw;} catch (SQLException e) {e.printStackTrace();return 0;}}/*** 某一個網(wǎng)址是否已經(jīng)存在* DBServer* @param sql* 查詢的sql語句*/public boolean isAddressExist(String sql) {try {ResultSet rs = mStatement.executeQuery(sql);if (rs.next()) {return true;}rs.close();} catch (SQLException e) {e.printStackTrace();}return false;}public void close() {try {if (mConn != null) {mConn.close();}if (mStatement != null) {mStatement.close();}} catch (Exception e) {e.printStackTrace();}} }
3.Java進行遞歸訪問鏈接
/*** 遍歷從某一節(jié)點開始的所有網(wǎng)絡(luò)鏈接* LinkSpider* @param startAddress* 開始的鏈接節(jié)點*/private static void ErgodicNetworkLink(String startAddress) {SpiderQueue queue = getAddressQueue(startAddress); // System.out.println(queue.toString());SpiderQueue auxiliaryQueue = null; // 記錄訪問某一個網(wǎng)頁中解析出的網(wǎng)址while (!queue.isQueueEmpty()) {WebInfoModel model = queue.poll();// TODO 判斷數(shù)據(jù)庫中是否已經(jīng)存在if (model == null || DBBLL.isWebInfoModelExist(model)) {continue;}// TODO 如果不存在就繼續(xù)訪問auxiliaryQueue = getAddressQueue(model.getAddres());System.out.println(auxiliaryQueue);// TODO 對已訪問的address進行入庫DBBLL.insert(model);if (auxiliaryQueue == null) {continue;}while (!auxiliaryQueue.isQueueEmpty()) {queue.offer(auxiliaryQueue.poll());}}}/*** 獲得某一鏈接下的所有合法鏈接* LinkSpider* @param htmlText* 網(wǎng)絡(luò)鏈接* @return*/private static SpiderQueue getAddressQueue(String htmlText) {if (htmlText == null) {return null;}SpiderQueue queue = PythonUtils.getAddressQueueByPython(htmlText);return queue;}
本程序的內(nèi)存及線程情況:
內(nèi)存:
線程:
爬取速度:
要點說明:
1.系統(tǒng)中的MySQL及MySQL包
? 你的Linux中必須要有MySQL,另外,我是通過Java來進行數(shù)據(jù)庫操作的,所以這里你的系統(tǒng)中也有要Mysql的Java驅(qū)動包。這一點在上面也有說明,不過這里還是要強調(diào)一下。如果你寫過JDBC的程序,那么這個驅(qū)動包,我想你應(yīng)該是有的,如果你沒寫過,那就去下一個吧。
2.需要一個輔助Queue
? 在上面的代碼中,我們可以看到我們有兩個SpiderQueue。一個是我們待訪問的隊列queue,保存我們將要訪問的鏈接列表;另一個是輔助隊列auxiliaryQueue,用于獲得從Python解析出來的數(shù)據(jù)。
3.使用圖的廣度優(yōu)先搜索算法進行鏈接爬取
? 這是從鏈接的相關(guān)性上考慮的。如果選擇深度優(yōu)先,那么隨著遍歷的深入,可能鏈接的相關(guān)性就會越來越小了。而廣度優(yōu)先搜索則不會這樣,因為我們都知道在同一個頁面中的鏈接總是會因為一些因素要展示在同一個頁面中,那么它們的相關(guān)性就會比較靠譜。
4.單線程與多線程
? 完成以上操作,如果你的程序正常運行。在前期是比較快的,可是到了穩(wěn)定期的時候就一般是1s鐘出一條數(shù)據(jù)。這個有點慢,我會在下一篇博客利用多線程來解決這個問題。
5.MySQL中添加一個叫cipher_address的字段
? 此字段用于address的加密生成(MD5 or SHA1)。下面舉個例子:
可能你有一個疑問,為什么要這個字段?如果你這樣思考了,那么對于你,是有益的。我們知道其實MySQL對一個很長的字符串進行select的時候,是相對來說比較慢的。這時,我們可以把這個address進行哈希一下,形成一個長度適中,又比較相近的字符串,這樣MySQL在比較時會容易一些(當(dāng)然,你可以不使用這個字段)。
6.OOM異常
? 完全按照本文中的代碼和講解來進行編碼的話,會獲得一個OOM的異常(我的程序是跑了1天半的時時間)。如下:
數(shù)量大概在23145條左右
對于這一點在上面關(guān)于內(nèi)存和線程的展示圖中可以看到原因。
總結(jié)
以上是生活随笔為你收集整理的网络爬虫初步:从一个入口链接开始不断抓取页面中的网址并入库的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java实现图的深度和广度优先遍历算法
- 下一篇: 网络爬虫:使用多线程爬取网页链接