被豆瓣反爬虫制裁实记录——温柔小薛的第一个小爬虫

写在前面

最近的课设是用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张图片

三、系统实现

文件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 ('')> start > html.find ('
    while html.find ('')> start > html.find ('):#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,'','')
    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,'','')
    return score

#函数六 获取引言
def movie_inq(html):
    inq = findall_in_html(html,'','')
    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()

除此之外还有两个图片文件,用于美化界面
运行结果
被豆瓣反爬虫制裁实记录——温柔小薛的第一个小爬虫_第2张图片
被豆瓣反爬虫制裁实记录——温柔小薛的第一个小爬虫_第3张图片

四、总结

学会了如何利用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
抓个包给大家瞅一眼吧
被豆瓣反爬虫制裁实记录——温柔小薛的第一个小爬虫_第4张图片
关于这个绕过呢,上面这篇文章没有写,我查了一下,一般都是用selenium进行绕过的,这个模块可以更加真实的模拟浏览器的操作,并且可以识别出这些蜜罐并绕过,关于比较高级的绕过,这篇文章写的挺好的

https://www.cnblogs.com/changkuk/p/10012547.html 常见表单反爬虫安全措施解密

就先写到这里吧,这次的爬虫不算成功,但是也算一次不错的尝试了,终于忙完学校的事情了,可以全心投入到安全学习上了

你可能感兴趣的:(#+,Python,python)