被豆瓣反爬蟲制裁實記錄——溫柔小薛的第一個小爬蟲

寫在前面

最近的課設是用python寫一個簡單爬蟲,然而不讓用使用起來便捷的第三方模塊和爬蟲框架,要求基於socket來寫,死磕了好幾天,結果還只能爬一丟丟啦,最後才查資料找到了問題所在,其實是遇到了簡單的蜜罐,聽了好久蜜罐,還真讓我遇上一次,不用第三方真的繞不過去啊,還好老師沒太難爲人,不過自己也是挺生氣哦,留個坑吧,以後抽時間再寫一個功能強的說啥也得繞過了!

不多說了,報告整上,做這個爬蟲需要一些web端知識哦,整體思路就是模擬瀏覽器發送request,接受返回的數據,再進行儲存,之後解析抽離出自己需要的數據,註釋都寫得很清楚了,只不過有些亂,沒寫的我會截圖說明,註釋的print都是我bebug用的

爬取目標是 https://movie.douban.com/top250?start=0&filter= 以及後面的頁面

一、需求分析

網絡爬蟲是從 web 中發現 ,下載以及存儲內容,是搜索引擎的核心部分。傳統爬蟲從一個或若干初始網頁的 URL 開始,獲得初始網頁上的 URL ,在抓取網頁的過程中,不斷從當前頁面上抽取新的 URL 放入隊列,直到滿足系統的一定停止條件。
選擇自己熟悉的開發環境和編程語言,實現網絡爬蟲抓取頁面、從而形成結構化數據的基本功能,界面適當美化。

二、系統設計

在本爬蟲程序中共有三個模塊:
1、爬蟲調度端:啓動爬蟲,停止爬蟲,監視爬蟲的運行情況。
2、爬蟲模塊:包含三個小模塊,URL 管理器、網頁下載器、網頁解析器。
(1)URL 管理器:對需要爬取的 URL 和已經爬取過的 URL 進行管理,可以從 URL管理器中取出一個待爬取的 URL,傳遞給網頁下載器。
(2)網頁下載器:網頁下載器將 URL 指定的網頁下載下來,存儲成一個字符串,傳遞給網頁解析器。
(3)網頁解析器:網頁解析器解析傳遞的字符串,解析器不僅可以解析出需要爬取的數據,而且還可以解析出每一個網頁指向其他網頁的 URL,這些 URL 被解析出來會補充進 URL 管理器
3、數據輸出模塊:存儲爬取的數據。
在這裏插入圖片描述

三、系統實現

文件1 爬蟲主體

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:XUE

# 目標是 https://movie.douban.com/top250?start=25&filter=
import socket
import ssl
import random
from tkinter import *
#網站的四個組成部分
#協議 主機 端口 路徑

#函數一 模仿瀏覽器訪問功能 構建發送GET請求 接收返回數據
def get (url):
    # 目標是 https://movie.douban.com/top250?start=25&filter=
    print('url是',url)
    u = url.split('://')[1] #切片獲取 協議 域名 路徑
    protocol = url.split('://')[0]
    i = u.find('/')
    host = u[:i]
    path = u[i:]
    if protocol == 'https':  #根據是http還是https 進行傳輸方式判斷
        s = ssl.wrap_socket(socket.socket())#創建實例 也可以說是建立連接
        port = 443 #默認端口
    else:
        s = socket.socket()#創建實例
        port = 80
        # print(protocol)
    s.connect((host,port))
    request = 'GET {} HTTP/1.1\r\nhost:{}\r\n\r\n'.format(path,host)
    # Cookie = {'bid=wPX71ZOfcqg': 'douban-fav-remind=1'}
    # print('request是',request)
    encoding = 'utf-8'
    s.send(request.encode(encoding))

    response = b''
    buffer_size = 1024
    while True :
        # print('response是',response)
        r = s.recv(buffer_size)
        response += r
        if len(r) < buffer_size:
            break

    response = response.decode(encoding)
    return response
    


#函數二 這裏有十個頁面 十個URL 寫一個函數來遍歷十個頁面 得到十個頁面源代碼
def htmls_from_douban():
    html = []#創建空列表
    url = """https://movie.douban.com/top250?start={}&filter=""" 
    for index in range (0,250,25):#依次是 0 25 50 ... 
        u = url.format(index) #把index傳入url  得到一個完整的url 就是u
        # print('url 是',u) #方便檢測
        r = get (u) #調用第一個函數 得到 字符串
        # print(' r是',r)
        html.append(r)  #把字符串添加到列表
    return html #得到一個列表 有十個元素 每個元素都是get函數得到的字符串
    print('htmls 是',list(html))

# print('htmls',htmls_from_douban())

#函數三 處理得到的頁面源代碼 這裏思路是例如想爬取這些電影的名字 我就去搜尋前後的關鍵字
#可以理解爲一個複雜的字符串操作
#假如調用這個函數的話,先找到一個標題,進入while循環
#把標題放入列表,再繼續查,查到再放入列表,最後跳出
def findall_in_html(html,startpart,endpart):#前後的字符串關鍵字
    all_strings = []#定義空列表
#find函數是字符串自帶的函數
#find()方法檢測字符串中是否包含子字符串str ,如果指定beg(開始)和 end(結束)範圍,
#則檢查是否包含在指定範圍內,如果包含子字符串返回開始的索引值,否則返回-1。
    start = html.find(startpart) + len(startpart) #得到起始下標 也就是>的位置
    end = html.find(endpart,start)#結尾下標 也就是從start開始找 也就是後半部分<的位置
    string = html[start:end]    #通過下標取到字符串 這裏採用切片
    # print('string是',string)
    #通過while循環 來滿足25個操作 (一頁25個標題)
    # print('進去了嗎',html.find ('</html>')> start > html.find ('<html'))
    while html.find ('</html>')> start > html.find ('<html'):#web頁面的頭到尾
        all_strings.append(string)
        start = html.find(startpart) + len(startpart)
        end = html.find(endpart,start)
        string = html[start:end]  
        # print('all_string是',list(all_strings))
        return all_strings


#函數四 處理獲取到的html中的中文電影名 因爲名字分了很多繁體字或者英文 所以不可能一步到位 
#注意這時候的這些名字是一個列表
def movie_name(html):
    # print('html是',html)
    name = findall_in_html(html,'<span class="title">','</span>')
    print('name是',name)
    for i in name:#利用一個for循環來處理得到的名字
        if 'nbsp' in i:
            name.remove(i)
    return name   
#函數五 獲取評分
def movie_score(html):
    score = findall_in_html(html,'<span class="rating_num" property="v:average">','</span>')
    return score

#函數六 獲取引言
def movie_inq(html):
    inq = findall_in_html(html,'<span class="inq">','</span>')
    return inq




#函數八 調用上面的函數來獲取數據
def movie_data_from_html(htmls):
    movie = []
    score = []
    inq = []
    
    # print('html2是',list(htmls))
    for h in htmls: #遍歷十個頁面 htmls是一個列表 十個元素 每個元素是一個html
        m = movie_name(h)
        s = movie_score(h)
        i = movie_inq(h)
        # print('m是',list(m))
        # print('s是',list(s))
        #extend 參數只接受列表 將列表中的元素添加到自己列表的後面
        movie.extend(m)
        score.extend(s)
        inq.extend(i)
    data = zip(movie,score,inq)#zip函數用來打包 把每個列表的一二三個元素 依此結合
    return data
    # print('data是',list(data))

#函數九 保存打印出的東西 相當於日誌 
#**args表示任何多個無名參數,它是一個tuple,Python將**args從開始到結束作爲一個tuple傳入函數
#**kwargs表示關鍵字參數,它是一個dict,Python將**kwargs從開始到結束作爲一個dict傳入函數
#函數根據定義對這個tuple或dict進行處理
def log (*args,**kwargs):
    with open ('movie.txt','a',encoding='utf-8') as f : #打開movie.txt文件
        print (*args,file=f,**kwargs)   
    #file是print的一個參數 file=f將print的輸出重定向到f 此用法多用於把日誌寫入文件


#函數十 主函數
def main():
    htmls = htmls_from_douban() #返回一個列表 十個元素 每個元素是一個html
    #print('html1是',list(htmls))
    movie_data = movie_data_from_html(htmls) #movie_data格式[('電影名','評分','引言','評分人數'),(),()]
    # print('movie_data是',list(movie_data))
    counter = 0 # 電影編號
    for item in movie_data:
        # print('item是',item)
        counter = counter + 1
        # print('是否log出來' + "NO." + str(counter))
        log('NO.'+str(counter))#字符串拼接
        log('電影名:',item[0])
        log('評分:',item[1])
        log('引言:',item[2])
  
        # movie_log('評分人數:',item[3],'\n\n')

#函數唯一入口 當單獨運行這個文件就運行main 當只是當模塊調用就不運行main 

# if __name__ == "__main__": 
# # print(__name__)
# # print("__main__")
#     main()

    

文件二 運行界面主體
注意要用多線程寫

from tkinter import *
import tkinter.messagebox
import test2
import os
import threading

def judge():
    t2.start()
def stop():
    tk.destroy()

def show():
    os.startfile(r'C:\Users\x\Desktop\py2\movie.txt')
    
def GUI():
    global tk
    tk=Tk() #實例化
    tk.title('豆瓣爬蟲 made by 薛世豪')  #標題
    tk.geometry('700x600') #窗口大小
    tk.iconbitmap("123456.ico") #左上角小圖標
    #標籤控件,顯示文本和位圖,展示在第一行
    Label(tk,text="點擊按鈕開始爬取").grid(row=0,sticky=E)# 第一行 靠右

    button1=Button(tk,text="啓動",font=('Arial',20),bg="orange", fg="red",command=judge)
    button1.grid(row=2,column=1)

    button2=Button(tk,text="退出",font=('Arial',20),bg="orange", fg="red",command=stop)
    button2.grid(row=4,column=1)

    button3=Button(tk,text="點擊查看結果",font=('Arial',20),bg="orange", fg="red",command=show)
    button3.grid(row=6,column=1)

    #插入圖片
    photo=PhotoImage(file="pa.gif")
    label=Label(image=photo)
    label.grid(row=0,column=2,rowspan=2,columnspan=2,sticky=W+E+N+S, padx=5, pady=5)
    #合併兩行,兩列,居中,四周外延5個長度

    #主事件循環
    mainloop()

t1 = threading.Thread(target=GUI)
t1.start()
t2 = threading.Thread(target=test2.main)

文件三 提示成功窗口
做的一個小彈窗 主函數巡行完後彈出 提示爬取成功

from tkinter import *
import tkinter.messagebox
import threading

def tanchu():
  tk=Tk() #實例化
  tk.geometry('300x200')
  Label(tk,text = '爬取完成',font=('Arial',20)).grid(row = 0,sticky=E)
  tk.mainloop()
def tanchu2():
  t1 = threading.Thread(target=tanchu) #觸發彈出
  t1.start()

除此之外還有兩個圖片文件,用於美化界面
運行結果
在這裏插入圖片描述
在這裏插入圖片描述

四、總結

學會了如何利用socket來模擬瀏覽器發送請求與接收回復的方式,有了編寫爬蟲爬取數據的基本思路,瞭解了現在比較主流的反爬蟲機制及應對手法,在對抗反爬蟲技術的技術方面還需要提升。

五、關於反爬蟲與繞過反爬蟲的小補充

現在主流的反爬蟲主要是:
1.請求頭檢查,比如cookies,user-agent,refer,甚至Accept-Language等等,這也是最基本的反爬機制。2.訪問頻次檢查,如果一個ip在短時間內訪問次服務器次數過於頻繁,且cookies相同,則會被判定爲機器人,你可能會被要求登錄後再訪問服務器或者輸入驗證碼,甚至直接封禁你的ip。3.驗證碼驗證,爬蟲無法輕易繞過這一關。4.有些網頁的元素是動態生成的,只有在js加載完成後纔會顯示。比如很多實用了Ajax的網站,都會有動態生成的元素。直接爬取頁面將無法獲取想要的元素。5.表單安全措施,如服務器生成的隨機變量加入字段檢測,提交表單蜜罐等。所謂蜜罐簡單來說就是騙機器人的一些表單,比如一下三種:

<a href='...' style='display:none;'> #看不見
<input type='hiden' ...> #隱藏
<input style='position:absolute; right:50000px;overflow-x:hidden;' ...> #右移50000像素,且隱藏滾動條,應該出電腦屏幕了

看不到如果你有關於這些元素操作,就表明你是直接通過頁面源碼觀察網頁的,也可以說明你是機器人,至少不是正常人。

這裏引用這篇文章:https://blog.csdn.net/Fourierrr_/article/details/79838128
scrapy實戰之與豆瓣反爬抗爭

這次我遇見的就是第二個,豆瓣管理員還命名個cat
抓個包給大家瞅一眼吧
在這裏插入圖片描述
關於這個繞過呢,上面這篇文章沒有寫,我查了一下,一般都是用selenium進行繞過的,這個模塊可以更加真實的模擬瀏覽器的操作,並且可以識別出這些蜜罐並繞過,關於比較高級的繞過,這篇文章寫的挺好的

https://www.cnblogs.com/changkuk/p/10012547.html 常見表單反爬蟲安全措施解密

就先寫到這裏吧,這次的爬蟲不算成功,但是也算一次不錯的嘗試了,終於忙完學校的事情了,可以全心投入到安全學習上了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章