大家好,本次為大家帶來的是抓取愛問知識人的問題并將問題和答案保存到數據庫的方法,涉及的內容包括:
* Urllib的用法及異常處理
* Beautiful Soup的簡單應用
* MySQLdb的基礎用法
* 正則表達式的簡單應用
## 環境配置
在這之前,我們需要先配置一下環境,我的Python的版本為2.7,需要額外安裝的庫有兩個,一個是Beautiful Soup,一個是MySQLdb,在這里附上兩個庫的下載地址,
[Beautiful Soup](https://pypi.python.org/pypi/beautifulsoup4/4.3.2)? ? ?[MySQLdb](http://sourceforge.net/projects/mysql-python/)
大家可以下載之后通過如下命令安裝
~~~
python setup.py install
~~~
環境配置好之后,我們便可以開心地擼爬蟲了
## 框架思路
首先我們隨便找一個分類地址,[外語學習 – 愛問知識人](http://iask.sina.com.cn/c/978-all-1.html),打開之后可以看到一系列的問題列表。
我們在這個頁面需要獲取的東西有:
總的頁碼數,每一頁的所有問題鏈接。
接下來我們需要遍歷所有的問題,來抓取每一個詳情頁面,提取問題,問題內容,回答者,回答時間,回答內容。
最后,我們需要把這些內容存儲到數據庫中。
## 要點簡析
其實大部分內容相信大家會了前面的內容,這里的爬蟲思路已經融匯貫通了,這里就說一下一些擴展的功能
### 1.日志輸出
日志輸出,我們要輸出時間和爬取的狀態,比如像下面這樣:
> [2015-08-10 03:05:20] 113011 號問題存在其他答案 我個人認為應該是櫻桃溝很美的
>
> [2015-08-10 03:05:20] 保存到數據庫,此問題的ID為 113011
>
> [2015-08-10 03:05:20] 當前爬取第 2 的內容,發現一個問題 百度有一個地方,花兒帶著芳香,水兒流淌奔騰是什么意思 多多幫忙哦 回答數量 1
>
> [2015-08-10 03:05:19] 保存到數據庫,此問題的ID為 113010
所以,我們需要引入時間函數,然后寫一個獲取當前時間的函數
~~~
import time
#獲取當前時間
def getCurrentTime(self):
????return time.strftime('[%Y-%m-%d %H:%M:%S]',time.localtime(time.time()))
????
#獲取當前時間
def getCurrentDate(self):
????return time.strftime('%Y-%m-%d',time.localtime(time.time()))
~~~
以上分別是獲取帶具體時間和獲取日期的函數,在輸出時,我們可以在輸出語句的前面調用這函數即可。
然后我們需要將緩沖區設置輸出到log中,在程序的最前面加上這兩句即可
~~~
f_handler=open('out.log', 'w')
sys.stdout=f_handler
~~~
這樣,所有的print語句輸出的內容就會保存到out.log文件中了。
### 2.頁碼保存
爬蟲爬取過程中可能出現各種各樣的錯誤,這樣會導致爬蟲的中斷,如果我們重新運行爬蟲,那么就會導致爬蟲從頭開始運行了,這樣顯然是不合理的。所以,我們需要把當前爬取的頁面保存下來,比如可以保存到文本中,假如爬蟲中斷了,重新運行爬蟲,讀取文本文件的內容,接著爬取即可。
大家可以稍微參考一下函數的實現:
~~~
????#主函數
????def main(self):
????????f_handler=open('out.log', 'w')
????????sys.stdout=f_handler
????????page = open('page.txt', 'r')
????????content = page.readline()
????????start_page = int(content.strip()) - 1
????????page.close()????
????????print self.getCurrentTime(),"開始頁碼",start_page
????????print self.getCurrentTime(),"爬蟲正在啟動,開始爬取愛問知識人問題"
????????self.total_num = self.getTotalPageNum()
????????print self.getCurrentTime(),"獲取到目錄頁面個數",self.total_num,"個"
????????if not start_page:
????????????start_page = self.total_num
????????for x in range(1,start_page):
????????????print self.getCurrentTime(),"正在抓取第",start_page-x+1,"個頁面"
????????????try:
????????????????self.getQuestions(start_page-x+1)
????????????except urllib2.URLError, e:
????????????????if hasattr(e, "reason"):
????????????????????print self.getCurrentTime(),"某總頁面內抓取或提取失敗,錯誤原因", e.reason
????????????except Exception,e:??
????????????????print self.getCurrentTime(),"某總頁面內抓取或提取失敗,錯誤原因:",e
????????????if start_page-x+1 start_page:
????????????????f=open('page.txt','w')
????????????????f.write(str(start_page-x+1))
????????????????print self.getCurrentTime(),"寫入新頁碼",start_page-x+1
????????????????f.close()
~~~
這樣,不管我們爬蟲中途遇到什么錯誤,媽媽也不會擔心了
### 3.頁面處理
頁面處理過程中,我們可能遇到各種各樣奇葩的HTML代碼,和上一節一樣,我們沿用一個頁面處理類即可。
~~~
import re
#處理頁面標簽類
class Tool:
????
????#將超鏈接廣告剔除
????removeADLink = re.compile('')
????#去除img標簽,1-7位空格,
????removeImg = re.compile('| {1,7}| ')
????#刪除超鏈接標簽
????removeAddr = re.compile('|')
????#把換行的標簽換為\n
????replaceLine = re.compile('|||')
????#將表格制表替換為\t
????replaceTD= re.compile('')
????#將換行符或雙換行符替換為\n
????replaceBR = re.compile('|')
????#將其余標簽剔除
????removeExtraTag = re.compile('')
????#將多行空行刪除
????removeNoneLine = re.compile('\n+')
????
????def replace(self,x):
????????x = re.sub(self.removeADLink,"",x)
????????x = re.sub(self.removeImg,"",x)
????????x = re.sub(self.removeAddr,"",x)
????????x = re.sub(self.replaceLine,"\n",x)
????????x = re.sub(self.replaceTD,"\t",x)
????????x = re.sub(self.replaceBR,"\n",x)
????????x = re.sub(self.removeExtraTag,"",x)
????????x = re.sub(self.removeNoneLine,"\n",x)
????????#strip()將前后多余內容刪除
????????return x.strip()
~~~
我們可以用一段含有HTML代碼的文字,經過調用replace方法之后,各種冗余的HTML代碼就會處理好了。
比如我們這么一段代碼:
~~~
article class="article-content">
h2>前言/h2>
p>最近發現MySQL服務隔三差五就會掛掉,導致我的網站和爬蟲都無法正常運作。自己的網站是基于MySQL,在做爬蟲存取一些資料的時候也是基于MySQL,數據量一大了,MySQL它就有點受不了了,時不時會崩掉,雖然我自己有網站監控和郵件通知,但是好多時候還是需要我來手動連接我的服務器重新啟動一下我的MySQL,這樣簡直太不友好了,所以,我就覺定自己寫個腳本,定時監控它,如果發現它掛掉了就重啟它。/p>
p>好了,閑言碎語不多講,開始我們的配置之旅。/p>
p>運行環境:strong>Ubuntu Linux 14.04/strong>/p>
h2>編寫Shell腳本/h2>
p>首先,我們要編寫一個shell腳本,腳本主要執行的邏輯如下:/p>
p>顯示mysqld進程狀態,如果判斷進程未在運行,那么輸出日志到文件,然后啟動mysql服務,如果進程在運行,那么不執行任何操作,可以選擇性輸出監測結果。/p>
p>可能大家對于shell腳本比較陌生,在這里推薦官方的shell腳本文檔來參考一下/p>
p>a href="http://wiki.ubuntu.org.cn/Shell%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80" data-original-title="" title="">Ubuntu Shell 編程基礎/a>/p>
p>shell腳本的后綴為sh,在任何位置新建一個腳本文件,我選擇在 /etc/mysql 目錄下新建一個 listen.sh 文件。/p>
p>執行如下命令:/p>
~~~
經過處理后便會變成如下的樣子:
~~~
前言
最近發現MySQL服務隔三差五就會掛掉,導致我的網站和爬蟲都無法正常運作。自己的網站是基于MySQL,在做爬蟲存取一些資料的時候也是基于MySQL,數據量一大了,MySQL它就有點受不了了,時不時會崩掉,雖然我自己有網站監控和郵件通知,但是好多時候還是需要我來手動連接我的服務器重新啟動一下我的MySQL,這樣簡直太不友好了,所以,我就覺定自己寫個腳本,定時監控它,如果發現它掛掉了就重啟它。
好了,閑言碎語不多講,開始我們的配置之旅。
運行環境:UbuntuLinux14.04
編寫Shell腳本
首先,我們要編寫一個shell腳本,腳本主要執行的邏輯如下:
顯示mysqld進程狀態,如果判斷進程未在運行,那么輸出日志到文件,然后啟動mysql服務,如果進程在運行,那么不執行任何操作,可以選擇性輸出監測結果。
可能大家對于shell腳本比較陌生,在這里推薦官方的shell腳本文檔來參考一下
UbuntuShell編程基礎
shell腳本的后綴為sh,在任何位置新建一個腳本文件,我選擇在/etc/mysql目錄下新建一個listen.sh文件。
執行如下命令:
~~~
經過上面的處理,所有亂亂的代碼都會被處理好了。
### 4.保存到數據庫
在這里,我們想實現一個通用的方法,就是把存儲的一個個內容變成字典的形式,然后執行插入語句的時候,自動構建對應的sql語句,插入數據。
比如我們構造如下的字典:
~~~
#構造最佳答案的字典
good_ans_dict = {
????????"text": good_ans[0],
????????"answerer": good_ans[1],
????????"date": good_ans[2],
????????"is_good": str(good_ans[3]),
????????"question_id": str(insert_id)
????????}
~~~
構造sql語句并插入到數據庫的方法如下:
~~~
#插入數據
????def insertData(self, table, my_dict):
???????? try:
???????????? self.db.set_character_set('utf8')
???????????? cols = ', '.join(my_dict.keys())
???????????? values = '"," '.join(my_dict.values())
???????????? sql = "INSERT INTO %s (%s) VALUES (%s)" % (table, cols, '"'+values+'"')
???????????? try:
???????????????? result = self.cur.execute(sql)
???????????????? insert_id = self.db.insert_id()
???????????????? self.db.commit()
???????????????? #判斷是否執行成功
???????????????? if result:
???????????????????? return insert_id
???????????????? else:
???????????????????? return 0
???????????? except MySQLdb.Error,e:
???????????????? #發生錯誤時回滾
???????????????? self.db.rollback()
???????????????? #主鍵唯一,無法插入
???????????????? if "key 'PRIMARY'" in e.args[1]:
???????????????????? print self.getCurrentTime(),"數據已存在,未插入數據"
???????????????? else:
???????????????????? print self.getCurrentTime(),"插入數據失敗,原因 %d: %s" % (e.args[0], e.args[1])
???????? except MySQLdb.Error,e:
???????????? print self.getCurrentTime(),"數據庫錯誤,原因%d: %s" % (e.args[0], e.args[1])
~~~
這里我們只需要傳入那個字典,便會構建出對應字典鍵值和鍵名的sql語句,完成插入。
### 5.PHP讀取日志
我們將運行結果輸出到了日志里,那么怎么查看日志呢?很簡單,在這里提供兩種方法
方法一:
PHP倒序輸出所有日志內容
~~~
html>
????head>
????????meta charset="utf-8">
????????meta http-equiv="refresh" content = "5">
????/head>
????body>
????????
????????????$fp = file("out.log");
????????????if ($fp) {
????????????????for($i = count($fp) - 1;$i >= 0; $i --)
????????????????echo $fp[$i]."";
????????????}
?????????>
????/body>
/html>
~~~
此方法可以看到所有的輸入日志,但是如果日志太大了,那么就會報耗費內存太大,無法輸出。為此我們就有了第二種方法,利用linux命令,輸出后十行內容。
方法二:
~~~
html>
????head>
????????meta charset="utf-8">
????????meta http-equiv="refresh" content = "5">
????/head>
????body>
????????
????????????$ph = popen('tail -n 100 out.log','r');
????????????while($r = fgets($ph)){
????????????????echo $r."";
????????????}
????????????pclose($ph);
?????????>
????/body>
/html>
~~~
上面兩種方法都是5秒刷新一次網頁來查看最新的日志。
## 源代碼放送
好了,閑言碎語不多講,直接上源碼了
~~~
spider.py
~~~
~~~
# -*- coding:utf-8 -*-
import urllib
import urllib2
import re
import time
import types
import page
import mysql
import sys
from bs4 import BeautifulSoup
class Spider:
????
????#初始化
????def __init__(self):
????????self.page_num = 1
????????self.total_num = None
????????self.page_spider = page.Page()
????????self.mysql = mysql.Mysql()
????????
????#獲取當前時間
????def getCurrentTime(self):
????????return time.strftime('[%Y-%m-%d %H:%M:%S]',time.localtime(time.time()))
????
????#獲取當前時間
????def getCurrentDate(self):
????????return time.strftime('%Y-%m-%d',time.localtime(time.time()))
????
????#通過網頁的頁碼數來構建網頁的URL
~~~
~~~
page.py
~~~
~~~
# -*- coding:utf-8 -*-
import urllib
import urllib2
import re
import time
import types
import tool
from bs4 import BeautifulSoup
#抓取分析某一問題和答案
class Page:
????
????def __init__(self):
????????self.tool = tool.Tool()
????
????#獲取當前時間
????def getCurrentDate(self):
????????return time.strftime('%Y-%m-%d',time.localtime(time.time()))
????
????#獲取當前時間
????def getCurrentTime(self):
????????return time.strftime('[%Y-%m-%d %H:%M:%S]',time.localtime(time.time()))
????#通過頁面的URL來獲取頁面的代碼
????def getPageByURL(self, url):
????????try:
????????????request = urllib2.Request(url)
????????????response = urllib2.urlopen(request)
????????????return response.read().decode("utf-8")
????????except urllib2.URLError, e:
~~~
~~~
tool.py
~~~
~~~
#-*- coding:utf-8 -*-
import re
#處理頁面標簽類
class Tool:
????
????#將超鏈接廣告剔除
????removeADLink = re.compile('')
????#去除img標簽,1-7位空格,
????removeImg = re.compile('| {1,7}| ')
????#刪除超鏈接標簽
????removeAddr = re.compile('|')
????#把換行的標簽換為\n
????replaceLine = re.compile('|||')
????#將表格制表替換為\t
????replaceTD= re.compile('')
????#將換行符或雙換行符替換為\n
????replaceBR = re.compile('|')
????#將其余標簽剔除
????removeExtraTag = re.compile('')
~~~
~~~
mysql.py
~~~
~~~
# -*- coding:utf-8 -*-
import MySQLdb
import time
class Mysql:
????
????#獲取當前時間
????def getCurrentTime(self):
????????return time.strftime('[%Y-%m-%d %H:%M:%S]',time.localtime(time.time()))
????
????#數據庫初始化
????def __init__(self):
????????try:
????????????self.db = MySQLdb.connect('ip','username','password','db_name')
????????????self.cur = self.db.cursor()
????????except MySQLdb.Error,e:
???????????? print self.getCurrentTime(),"連接數據庫錯誤,原因%d: %s" % (e.args[0], e.args[1])
????#插入數據
????def insertData(self, table, my_dict):
???????? try:
???????????? self.db.set_character_set('utf8')
???????????? cols = ', '.join(my_dict.keys())
???????????? values = '"," '.join(my_dict.values())
???????????? sql = "INSERT INTO %s (%s) VALUES (%s)" % (table, cols, '"'+values+'"')
???????????? try:
???????????????? result = self.cur.execute(sql)
???????????????? insert_id = self.db.insert_id()
???????????????? self.db.commit()
???????????????? #判斷是否執行成功
???????????????? if result:
???????????????????? return insert_id
???????????????? else:
???????????????????? return 0
???????????? except MySQLdb.Error,e:
???????????????? #發生錯誤時回滾
???????????????? self.db.rollback()
???????????????? #主鍵唯一,無法插入
???????????????? if "key 'PRIMARY'" in e.args[1]:
???????????????????? print self.getCurrentTime(),"數據已存在,未插入數據"
???????????????? else:
???????????????????? print self.getCurrentTime(),"插入數據失敗,原因 %d: %s" % (e.args[0], e.args[1])
???????? except MySQLdb.Error,e:
???????????? print self.getCurrentTime(),"數據庫錯誤,原因%d: %s" % (e.args[0], e.args[1])
~~~
數據庫建表SQL如下:
~~~
CREATE TABLE IF NOT EXISTS `iask_answers` (
??`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
??`text` text NOT NULL COMMENT '回答內容',
??`question_id` int(18) NOT NULL COMMENT '問題ID',
??`answerer` varchar(255) NOT NULL COMMENT '回答者',
??`date` varchar(255) NOT NULL COMMENT '回答時間',
??`is_good` int(11) NOT NULL COMMENT '是否是最佳答案',
??PRIMARY KEY (`id`)
) ENGINE=InnoDB??DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `iask_questions` (
??`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '問題ID',
??`text` text NOT NULL COMMENT '問題內容',
??`questioner` varchar(255) NOT NULL COMMENT '提問者',
??`date` date NOT NULL COMMENT '提問時間',
??`ans_num` int(11) NOT NULL COMMENT '回答數量',
??`url` varchar(255) NOT NULL COMMENT '問題鏈接',
??PRIMARY KEY (`id`)
) ENGINE=InnoDB??DEFAULT CHARSET=utf8;
~~~
運行的時候執行如下命令即可
~~~
nohup python spider.py &
~~~
代碼寫的不好,僅供大家學習參考使用,如有問題,歡迎留言交流。
## 運行結果查看
我們把PHP文件和log文件放在同一目錄下,運行PHP文件,便可以看到如下的內容:
[](http://qiniu.cuiqingcai.com/wp-content/uploads/2015/09/20150908222744.png)
小伙伴們趕快試一下吧。
- Python爬蟲入門
- (1):綜述
- (2):爬蟲基礎了解
- (3):Urllib庫的基本使用
- (4):Urllib庫的高級用法
- (5):URLError異常處理
- (6):Cookie的使用
- (7):正則表達式
- (8):Beautiful Soup的用法
- Python爬蟲進階
- Python爬蟲進階一之爬蟲框架概述
- Python爬蟲進階二之PySpider框架安裝配置
- Python爬蟲進階三之Scrapy框架安裝配置
- Python爬蟲進階四之PySpider的用法
- Python爬蟲實戰
- Python爬蟲實戰(1):爬取糗事百科段子
- Python爬蟲實戰(2):百度貼吧帖子
- Python爬蟲實戰(3):計算大學本學期績點
- Python爬蟲實戰(4):模擬登錄淘寶并獲取所有訂單
- Python爬蟲實戰(5):抓取淘寶MM照片
- Python爬蟲實戰(6):抓取愛問知識人問題并保存至數據庫
- Python爬蟲利器
- Python爬蟲文章
- Python爬蟲(一)--豆瓣電影抓站小結(成功抓取Top100電影)
- Python爬蟲(二)--Coursera抓站小結
- Python爬蟲(三)-Socket網絡編程
- Python爬蟲(四)--多線程
- Python爬蟲(五)--多線程續(Queue)
- Python爬蟲(六)--Scrapy框架學習
- Python爬蟲(七)--Scrapy模擬登錄
- Python筆記
- python 知乎爬蟲
- Python 爬蟲之——模擬登陸
- python的urllib2 模塊解析
- 蜘蛛項目要用的數據庫操作
- gzip 壓縮格式的網站處理方法
- 通過瀏覽器的調試得出 headers轉換成字典
- Python登錄到weibo.com
- weibo v1.4.5 支持 RSA協議(模擬微博登錄)
- 搭建Scrapy爬蟲的開發環境
- 知乎精華回答的非專業大數據統計
- 基于PySpider的weibo.cn爬蟲
- Python-實現批量抓取妹子圖片
- Python庫
- python數據庫-mysql
- 圖片處理庫PIL
- Mac OS X安裝 Scrapy、PIL、BeautifulSoup
- 正則表達式 re模塊
- 郵件正則
- 正則匹配,但過濾某些字符串
- dict使用方法和快捷查找
- httplib2 庫的使用