写在前面
最近的课设是用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 常见表单反爬虫安全措施解密
就先写到这里吧,这次的爬虫不算成功,但是也算一次不错的尝试了,终于忙完学校的事情了,可以全心投入到安全学习上了