1. 程式人生 > >Python爬蟲學習,實戰一糗事百科(2017/7/21更新)

Python爬蟲學習,實戰一糗事百科(2017/7/21更新)

前言

這幾天學習爬蟲,網上看了一些教程,發現這個 http://cuiqingcai.com/990.html 是相當不錯的。

但可惜的是,整個教程是兩年前的,但是Python是2.x版本的,跟現在的3.x有一些基本的語法不同;還有糗事百科也經過了改版。
總之原來的爬蟲程式已經無法運行了。

藉此學習機會,我更新一下這篇文章。

目標程序

  1. 本身初學python,暫時用著python2,完成這次爬蟲實驗
  2. 文章順序按照原文章進行
  3. 分析原始碼構成,並進行修改以適應現在的糗事
  4. 最後改成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為例:

  1. 在網頁任意地方右擊選擇審查元素或者按下 shift+ctrl+c開啟chrome自帶的除錯工具;
  2. 選擇network標籤,重新整理網頁(在開啟除錯工具的情況下重新整理);
  3. 重新整理後在左邊查詢該網頁url(網址),點選
  4. 後右邊選擇headers,就可以看到當前網頁的http頭了;
  5. 我們用的自然是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)

糗百改版:

這裡寫圖片描述

  1. 釋出人:

    1. 從div class=”author”到div class=”author clearfix”
    2. 原來只有一個釋出人的href,img後緊跟名字; 現在有兩個,名字在第二個中的正文裡
  2. 內容:沒有改變

  3. 時間:已經刪去此功能

  4. 圖片:沒有修改,在內容和點贊之間的thumb裡

  5. 點贊數:沒有修改

修改完成後的總程式碼是和效果圖

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()