Python爬蟲學習,實戰一糗事百科(2017/7/21更新)
阿新 • • 發佈:2019-02-06
前言
這幾天學習爬蟲,網上看了一些教程,發現這個 http://cuiqingcai.com/990.html 是相當不錯的。
但可惜的是,整個教程是兩年前的,但是Python是2.x版本的,跟現在的3.x有一些基本的語法不同;還有糗事百科也經過了改版。
總之原來的爬蟲程式已經無法運行了。
藉此學習機會,我更新一下這篇文章。
目標程序
- 本身初學python,暫時用著python2,完成這次爬蟲實驗
- 文章順序按照原文章進行
- 分析原始碼構成,並進行修改以適應現在的糗事
- 最後改成Python3(未完成)
1.確定URL並頁面程式碼
現在的糗百URL為https://www.qiushibaike.com(熱門板塊) ,當然你可以進去到https://www.qiushibaike.com/hot/(24小時板塊)
我們還是以熱門板塊來做,刷了幾頁過後發現,他是這樣的: https://www.qiushibaike.com/8hr/page/5/?s=5001478,後面一堆玩意兒看不懂,
不過試了試https://www.qiushibaike.com/8hr/page/4,也是沒問題,那麼我們的URL就可以出來了
結合原文中的程式碼,我們這麼寫:
# -*- coding:utf-8 -*-
import urllib
import urllib2
page = 1
url = 'https://www.qiushibaike.com/8hr/page/' + str(page)
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = { 'User-Agent' : user_agent }
try:
request = urllib2.Request(url,headers = headers)
response = urllib2.urlopen(request)
print response.read()
except urllib2.URLError, e:
if hasattr(e,"code"):
print e.code
if hasattr(e,"reason"):
print e.reason
2.關於headers驗證
這個headers是用來判斷網站訪問這是否是通過瀏覽器訪問的。
網上關於怎麼查詢headers很齊全。
這裡以chrome為例:
- 在網頁任意地方右擊選擇審查元素或者按下 shift+ctrl+c開啟chrome自帶的除錯工具;
- 選擇network標籤,重新整理網頁(在開啟除錯工具的情況下重新整理);
- 重新整理後在左邊查詢該網頁url(網址),點選
- 後右邊選擇headers,就可以看到當前網頁的http頭了;
- 我們用的自然是Request Headers
3.解碼,正則表示式分析
解碼:
利用2的程式碼我們可以抓取到網頁的程式碼,但看上去似乎是一堆亂碼,這個時候我們只需要將原始的讀取稍微轉化一下
response.read()變成response.read().decode('utf-8')
正則表示式
我們先來看原始碼和原頁面
content = response.read().decode('utf-8')
pattern = re.compile('<div.*?author">.*?<a.*?<img.*?>(.*?)</a>.*?<div.*?'+
'content">(.*?)<!--(.*?)-->.*?</div>(.*?)<div class="stats.*?class="number">(.*?)</i>',re.S)
items = re.findall(pattern,content)
for item in items:
print item[0],item[1],item[2],item[3],item[4]
現在正則表示式在這裡稍作說明
1).*?
是一個固定的搭配,.和*代表可以匹配任意無限多個字元,加上?表示使用非貪婪模式進行匹配,也就是我們會盡可能短地做匹配,以後我們還會大量用到
.*? 的搭配。2)(.?)代表一個分組,在這個正則表示式中我們匹配了五個分組,在後面的遍歷item中,item[0]就代表第一個(.?)所指代的內容,item[1]就代表第二個(.*?)所指代的內容,以此類推。
3)re.S 標誌代表在匹配時為點任意匹配模式,點 . 也可以代表換行符。
這組樣例沒有圖片:
分析(不考慮”+換行的問題)
pattern = re.compile(
'<div.*?author">.*?
<a.*?
<img.*?>(.*?)</a>.*?//釋出人
<div.*?'+'content">(.*?)//內容
<!--(.*?)-->.*?//時間
</div>(.*?)<div class="stats.*?//圖片內容
class="number">(.*?)</i>'#點贊數
,re.S)
糗百改版:
釋出人:
- 從div class=”author”到div class=”author clearfix”
- 原來只有一個釋出人的href,img後緊跟名字; 現在有兩個,名字在第二個中的正文裡
內容:沒有改變
時間:已經刪去此功能
圖片:沒有修改,在內容和點贊之間的thumb裡
點贊數:沒有修改
修改完成後的總程式碼是和效果圖
import urllib
import urllib2
import re
page = 1
url = 'http://www.qiushibaike.com/hot/page/' + str(page)
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = { 'User-Agent' : user_agent }
try:
request = urllib2.Request(url,headers = headers)
response = urllib2.urlopen(request)
content = response.read().decode('utf-8')
pattern = re.compile('<div.*?author clearfix">.*?</a>.*?>(.*?)</a>.*?'+
'<div.*?content">.*?<span>(.*?)</span>(.*?)'+
'<div class="stats.*?class="number">(.*?)</i>',re.S)
items = re.findall(pattern,content)
for item in items:
haveImg = re.search("img",item[2])
if not haveImg:
print "釋出人: ",item[0],"內容:",item[1],"\n點贊數:",item[3]+'\n'
except urllib2.URLError, e:
if hasattr(e,"code"):
print e.code
if hasattr(e,"reason"):
print e.reason
4.完善互動,設計面向物件模式
照著原始碼,稍微改改吧
import urllib
import urllib2
import re
import thread
import time
class QSBK:
#初始化方法,定義一些變數
def __init__(self):
self.pageIndex = 1
self.user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
#初始化headers
self.headers = { 'User-Agent' : self.user_agent }
#存放段子的變數,每一個元素是每一頁的段子們
self.stories = []
#存放程式是否繼續執行的變數
self.enable = False
#傳入某一頁的索引獲得頁面程式碼
def getPage(self,pageIndex):
try:
url = 'https://www.qiushibaike.com/8hr/page/' + str(pageIndex)
#構建請求的request
request = urllib2.Request(url,headers = self.headers)
#利用urlopen獲取頁面程式碼
response = urllib2.urlopen(request)
#將頁面轉化為UTF-8編碼
pageCode = response.read().decode('utf-8')
return pageCode
except urllib2.URLError, e:
if hasattr(e,"reason"):
print u"連線糗事百科失敗,錯誤原因",e.reason
return None
#傳入某一頁程式碼,返回本頁不帶圖片的段子列表
def getPageItems(self,pageIndex):
pageCode = self.getPage(pageIndex)
if not pageCode:
print "頁面載入失敗...."
return None
pattern = re.compile('<div.*?author clearfix">.*?</a>.*?>(.*?)</a>.*?'+
'<div.*?content">.*?<span>(.*?)</span>(.*?)'+
'<div class="stats.*?class="number">(.*?)</i>',re.S)
items = re.findall(pattern,pageCode)
#用來儲存每頁的段子們
pageStories = []
#遍歷正則表示式匹配的資訊
for item in items:
#是否含有圖片
haveImg = re.search("img",item[3])
#如果不含有圖片,把它加入list中
if not haveImg:
replaceBR = re.compile('<br/>')
text = re.sub(replaceBR,"\n",item[1])
#item[0]是一個段子的釋出者,item[1]是內容,item[3]是點贊數
pageStories.append([item[0].strip(),text.strip(),item[3].strip()])
return pageStories
#載入並提取頁面的內容,加入到列表中
def loadPage(self):
#如果當前未看的頁數少於2頁,則載入新一頁
if self.enable == True:
if len(self.stories) < 2:
#獲取新一頁
pageStories = self.getPageItems(self.pageIndex)
#將該頁的段子存放到全域性list中
if pageStories:
self.stories.append(pageStories)
#獲取完之後頁碼索引加一,表示下次讀取下一頁
self.pageIndex += 1
#呼叫該方法,每次敲回車列印輸出一個段子
def getOneStory(self,pageStories,page):
#遍歷一頁的段子
for story in pageStories:
#等待使用者輸入
input = raw_input()
#每當輸入回車一次,判斷一下是否要載入新頁面
self.loadPage()
#如果輸入Q則程式結束
if input == "Q":
self.enable = False
return
print u"第%d頁\t釋出人:%s\t贊:%s\n%s\n" %(page,story[0],story[2],story[1])
#開始方法
def start(self):
print u"正在讀取糗事百科,按回車檢視新段子,Q退出"
#使變數為True,程式可以正常執行
self.enable = True
#先載入一頁內容
self.loadPage()
#區域性變數,控制當前讀到了第幾頁
nowPage = 0
while self.enable:
if len(self.stories)>0:
#從全域性list中獲取一頁的段子
pageStories = self.stories[0]
#當前讀到的頁數加一
nowPage += 1
#將全域性list中第一個元素刪除,因為已經取出
del self.stories[0]
#輸出該頁的段子
self.getOneStory(pageStories,nowPage)
spider = QSBK()
spider.start()