爬取简单的数据的总体思路是这样的:获取网页信息---解析页面信息---模拟翻页---匹配所需数据---下载到本地文件
爬取数据
第一步:分析网页信息,确定好思路
我选择的目标网址是彼岸桌面壁纸:http://www.netbian.com/
工具:firefox浏览器, pycharm
人工下载图片的步骤:点开网页,找到大图,下载大图到硬盘.
如果想下载一百张或者一千张,那这样的操作会非常耗精力和时间,而爬虫的作用就是模拟人工操作,解放双手,自动下载。
爬虫本质上就是模拟用户操作,我们要做的就是将人工的操作一步一步写成代码的形式即可。
下面将从第一步开始,一步一步分析是如何写一个小爬虫的。
我们进入网页后,按F12进入开发者模式,用选择器选择要下载的图片
,可以注意到有一个src
指向了该图片的url
,如果把该连接复制到地址栏回车,会发现是一张小图,不是我们所需要的高清图片。这时我们点开图片,再用开发者模式可以注意到一个图片连接
http://img.netbian.com/file/2020/0817/37f2656b96ebcd22dab05f4c0892bcc4.jpg
将这个地址打开后,会发现是一张高清大图,这也就是我们需要的数据了。
在回过头分析第一页的图片信息,可以注意到我们要的图片信息都在一个list
的class类
内,这一点要记住,后续会用到:
同时我们也能注意到点开一张新图后的地址:
就藏在了<a href="/desk/22815.htm"...>
内
分析到这里,那么一个爬虫的操作就是:在第一层页面寻找到一张图片的herf所指向的地址后,利用该地址进去到第二层后找到该图片的 src= 指向的地址,将该地址下载到本地即可
这是第一页的信息,第二页的信息也是同理,但是爬虫怎么做到自动翻页呢?
同样F12进入开发者模式,鼠标移到页码上去,会发现页码地址的格式是这样的:
http://www.netbian.com/index_n.htm (n表示页码数字)
这里需要着重注意一下,可以发现除了第一个页码是http://www.netbian.com/
以外,其他的都是上面那种inde_n的格式
那么翻页只需要改动
index_n
后的数字n即可了。
至此网页信息分析完毕。
第二步:写一个正则,匹配所需内容
这里介绍一个查看正则匹配的网站:Online regex tester
第一步介绍了那些网页数据,我们将其摘下来:
1:访问该图片的地址
<a href="/desk/22815.htm" title="女孩 房间 猫 玩具熊 独处 小提琴 好看动漫人物壁纸" target="_blank">
中的 /desk/22815.htm
2:该图片的url地址:
<img src="http://img.netbian.com/file/2020/0817/37f2656b96ebcd22dab05f4c0892bcc4.jpg" alt="女孩 房间 猫 玩具熊 独处 小提琴 好看动漫人物壁纸" title="女孩 房间 猫 玩具熊 独处 小提琴 好看动漫人物壁纸">
中的 http://img.netbian.com/file/2020/0817/37f2656b96ebcd22dab05f4c0892bcc4.jpg
3:翻页方式
http://www.netbian.com/index_n.htm
对于第一,二点,观察数据的规律可以利用正则表达式匹配出来:
layer1 = r'href="/(.*?htm)"' #第一层数据,访问该图片的大图页面
layer2 = r'<img src="(.*?)" alt=' #第二层数据,寻找大图的地址,由于我们只需要第一张图即可,所以不用贪婪匹配,第一个就可以了。
如果对正则表达式不是很自信,那么可以在Online regex tester内尝试着匹配,成功了在写进代码内。
具体操作如下:
查看网页源代码(鼠标点网页空白处查看),将源代码复制进TESE STRING
内,正则表达式写在REGULAR EXPERSSION
内,如下图所示:
可以观察到正则匹配没问题,的确可以这样匹配。
匹配数据找完了,翻页的话,构造字符串http://www.netbian.com/+index_{}.format(n)
,对n进行for循环
便可完成翻页,这个基础操作不用细说。
第三步:解析网页数据,利用正则寻找数据
获取当前界面的信息,我用的是 requsets模块,当然也能用urllib库
步骤一:获取响应对象
def getHtml(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0'
}
html = requests.get(url, headers = headers, timeout=5) #伪装成浏览器访问
html.encoding = 'gbk' #解码格式按照网页要求来
return html
代码解释:
为了不让服务器察觉我们是一个爬虫,所以我们需要把自己伪装成一个浏览器,在
get
方法内传入参数响应头,内部的User-Agent
会告诉服务器访问者是通过什么工具来请求的。要查看自己的浏览器是什么响应头可以看这篇文章:HTTP请求头之User-Agent
为啥后面一个timeout
要设置成5
,是因为我在测试爬虫的时候,发现爬取到第7页
的时候服务器无响应了,百度找到原因说是这里需要设置一个请求时间。
返回的对象用html
存放,并根据网页编码格式设置编码,这里我查看到壁纸网页用的gbk
编码格式,这里一般默认是utf-8
,所以有些时候出现乱码的话,需要更改一下。可以用下面的方式自动设置编码,就不用自己查看了。
最后将获取的响应对象return出去
如果不想手动查看网页编码格式,那么可以加上这两行来自动获取网页编码格式:
code = re.findall(r'<meta charset="(.*?)" />', text)[0] #获取网页编码格式
html.encoding = code #采用网页的编码格式
步骤二:获取网页信息
html1 = getHtml(url) #第一层网页 获取图片的访问地址
print(url)
soup = BeautifulSoup(html1.content, "html.parser") #html解析器
_list = str(soup.find_all(class_="list")[0]) #找到类list的所有数据
layer1 = r'href="/(.*?htm)"' #第一层网页的正则匹配,获取图片链接
datalist = re.findall(layer1, _list)
layer2 = r'<img src="(.*?)" alt=' #第二层网页,获取图片地址
jpglinks = [] #存放图片的访问地址
for data in datalist:
html2 = getHtml(baseurl+data).text #获取大图网页的响应对象
jpg = re.findall(layer2, html2)[0] #正则匹配大图的下载地址
jpglinks.append(jpg) #将图片链接放进jpglinks列表内
downData(jpglinks, page, 1) #下载数据
代码解释:
这段代码完成了两个功能。
1:从第一层图片界面获取到17个图片的大图访问地址并存放到一个列表中。
2:从该列表中分析出大图的下载地址并放入一个jpglinks
的列表中,传入到downData()
方法中去下载。
由第一步分析可以知道第一层页面的17张图片都在class = list
的类内,所以用BeautifulSoup
对象可以解析出所有的class=list
的内容并放在一个列表内,如果输出该列表的结果:
[<div class="list">
<ul>
<li><a href="/desk/22815.htm" target="_blank" title="女孩 房间 猫 玩具熊 独处 小提琴 好看动漫人物壁纸"><img alt="女孩 房间 猫 玩具熊 独处 小提琴 好看动漫人物壁纸" src="http://img.netbian.com/file/2020/0817/small37f2656b96ebcd22dab05f4c0892bcc41597595187.jpg"/><b>女孩 房间 猫 玩具熊 独处 小提琴 好看动漫人物壁纸</b></a></li><li><a href="/desk/22814.htm" target="_blank" title="《Lost in Random》高清游戏壁纸"><img alt="《Lost in Random》高清游戏壁纸" src="http://img.netbian.com/file/2020/0815/small0b33738aa52a268b46dce17b0e890be91597507162.jpg"/><b>《Lost in Random》高清游戏壁纸</b></a></li><li>
<div class="pic_box"><a href="http://pic.netbian.com/" target="_blank"><img alt="4K/5K/8K高清壁纸" src="http://img.netbian.com/file/2020/0702/7a637948df7c661b616d7fcf03e9d04c.jpg"/></a></div><p><a href="http://pic.netbian.com/" style="color:#FF0000;" target="_blank">【4K/5K/8K超清壁纸】</a></p>
</li><li><a href="/desk/22813.htm" target="_blank" title="《地狱时刻(Hellpoint)》2k游戏壁纸"><img alt="《地狱时刻(Hellpoint)》2k游戏壁纸" src="http://img.netbian.com/file/2020/0815/small75ac6cf205206553ed348d62e414469e1597507104.jpg"/><b>《地狱时刻(Hellpoint)》2k游戏壁纸</b></a></li><li><a href="/desk/22812.htm" target="_blank" title="女孩子仰臥 好看动漫美腿美女电脑壁纸"><img alt="女孩子仰臥 好看动漫美腿美女电脑壁纸" src="http://img.netbian.com/file/2020/0814/smallfbb1db7f27e748086d30301c6540918f1597420033.jpg"/><b>女孩子仰臥 好看动漫美腿美女电脑壁纸</b></a></li><li><a href="/desk/22811.htm" target="_blank" title="可爱微笑礼仪手势大学生美女高清壁纸"><img alt="可爱微笑礼仪手势大学生美女高清壁纸" src="http://img.netbian.com/file/2020/0814/small04f08e79c7dbdfd2c394df768da784bb1597419809.jpg"/><b>可爱微笑礼仪手势大学生美女高清壁纸</b></a></li><li><a href="/desk/22810.htm" target="_blank" title="陈明日方舟高清壁纸"><img alt="陈明日方舟高清壁纸" src="http://img.netbian.com/file/2020/0813/small188628f088e917cc5c16d4d1e87163cb1597315060.jpg"/><b>陈明日方舟高清壁纸</b></a></li><li><a href="/desk/22809.htm" target="_blank" title="帅气酷飒运动美女好看动漫美女壁纸"><img alt="帅气酷飒运动美女好看动漫美女壁纸" src="http://img.netbian.com/file/2020/0811/smalldf4fecb7ab331a36435209eb7248146f1597152603.jpg"/><b>帅气酷飒运动美女好看动漫美女壁纸</b></a></li><li><a href="/desk/22808.htm" target="_blank" title="早晨桂林漓江山水风景高清壁纸"><img alt="早晨桂林漓江山水风景高清壁纸" src="http://img.netbian.com/file/2020/0811/smalla57f1647f17b5d7f09f87b58f171f6a11597152434.jpg"/><b>早晨桂林漓江山水风景高清壁纸</b></a></li><li><a href="/desk/22807.htm" target="_blank" title="拉弓女孩 绿色眼睛 高清好看漂亮动漫女生壁纸"><img alt="拉弓女孩 绿色眼睛 高清好看漂亮动漫女生壁纸" src="http://img.netbian.com/file/2020/0811/smallcda7673ed09e765c8c6213244c50bb481597151640.jpg"/><b>拉弓女孩 绿色眼睛 高清好看漂亮动漫女生壁纸</b></a></li><li><a href="/desk/22806.htm" target="_blank" title="大侠霍元甲赵文卓海报高清壁纸"><img alt="大侠霍元甲赵文卓海报高清壁纸" src="http://img.netbian.com/file/2020/0810/small5f3434bb3cb83201910df895a42cd7691597067689.jpg"/><b>大侠霍元甲赵文卓海报高清壁纸</b></a></li><li><a href="/desk/22805.htm" target="_blank" title="晚上星空风景 看烟花的女孩2k高清壁纸"><img alt="晚上星空风景 看烟花的女孩2k高清壁纸" src="http://img.netbian.com/file/2020/0809/small1200106635d0f186f62877257fb3607a1596944142.jpg"/><b>晚上星空风景 看烟花的女孩2k高清壁纸</b></a></li><li><a href="/desk/22804.htm" target="_blank" title="梦幻城堡 公主 阳台 唯美2k动漫壁纸"><img alt="梦幻城堡 公主 阳台 唯美2k动漫壁纸" src="http://img.netbian.com/file/2020/0809/small1d5c15517a862b351ae447ec6c75cf4d1596943883.jpg"/><b>梦幻城堡 公主 阳台 唯美2k动漫壁纸</b></a></li><li><a href="/desk/22803.htm" target="_blank" title="七夕阴阳师高清壁纸"><img alt="七夕阴阳师高清壁纸" src="http://img.netbian.com/file/2020/0808/small5a9362a5901e897263c9109fb12fa8431596819581.jpg"/><b>七夕阴阳师高清壁纸</b></a></li><li><a href="/desk/22802.htm" target="_blank" title="LOL英雄联盟灵魂莲华节 阿狸 莉莉娅 锐雯 薇恩高清壁纸"><img alt="LOL英雄联盟灵魂莲华节 阿狸 莉莉娅 锐雯 薇恩高清壁纸" src="http://img.netbian.com/file/2020/0808/small0e26ca2e82c97f16aa3fb4f8ede9fc241596819358.jpg"/><b>LOL英雄联盟灵魂莲华节 阿狸 莉莉娅 锐雯 薇恩高清壁纸</b></a></li><li><a href="/desk/22801.htm" target="_blank" title="云鹰飞将曜 王者荣耀高清壁纸"><img alt="云鹰飞将曜 王者荣耀高清壁纸" src="http://img.netbian.com/file/2020/0807/small1871233011c1435935405e4f52f53ba11596776676.jpg"/><b>云鹰飞将曜 王者荣耀高清壁纸</b></a></li><li><a href="/desk/22800.htm" target="_blank" title="三国杀 玉脂牵芯SP貂蝉高清壁纸"><img alt="三国杀 玉脂牵芯SP貂蝉高清壁纸" src="http://img.netbian.com/file/2020/0805/small415217bd8b1a785faa97b0041d7546c91596642038.jpg"/><b>三国杀 玉脂牵芯SP貂蝉高清壁纸</b></a></li><li><a href="/desk/22799.htm" target="_blank" title="三国杀踏云羽升 诸葛果高清壁纸"><img alt="三国杀踏云羽升 诸葛果高清壁纸" src="http://img.netbian.com/file/2020/0805/small0cdfd13a01b8a0069285e8244f97ff311596641950.jpg"/><b>三国杀踏云羽升 诸葛果高清壁纸</b></a></li> </ul>
</div>]
会发现刚好就是那些图片所在的内容,后续利用正则即可提取出来,这里一定要注意,提取出来的连接是:
desk/22815.htm
,必须还加上http://www.netbian.com/
这个也是我们基本
的url
地址:
baseUrl = 'http://www.netbian.com/'
步骤三:下载到本地
def downData(jpglinks, page, num = 1):
newpath = r'.\彼岸桌面\页码{}'.format(page) #指定的文件夹
try:
t = os.makedirs(newpath) #创建新的文件夹
except FileExistsError :
print('文件夹存在,请删除文件夹 {} 再操作'.format(os.path.dirname(os.getcwd()+newpath.replace('.','')))) #打印出删除要删除的文件夹
else:
for jpg in jpglinks:
path = newpath+'\图片{}.jpg'.format(num) #图片命名采用图片1,图片2来
urlretrieve(jpg, path ,downPercent) #下载到指定文件夹
num += 1
代码解释:
传入的参数分别对应着图片的下载链接地址(
jpglinks
),当前页码(page
),个数(num
),默认个数从1开始。
这里用到了库from urllib.request import urlretrieve
这个也是下载文件所需的方法。
在代码urlretrieve(jpg, path ,downPercent) #下载到指定文件夹
中,urlretrieve
将从jpg地址中
,下载到路径path
中,第三个参数downPercent
是用来观察下载速度的。
这里我是写到了下载到当前项目所在的文件中。可以修改。
步骤四:模拟翻页
def pageHtml():
nextHtml = baseurl+'/index_{}.htm' #下一页的格式
for page in range(1,11): #获取当前页的内容
getData(page, nextHtml.format(page) if page > 1 else baseurl ) #从第二页开始就采用下一页的格式
代码解释:
模拟翻页,就是用
循环(for,while)
来处理即可,从第1页
开始到第n页
结束,在处理翻页的过程中,因为第一页是不需要加/index_n
的,所以需要单独处理一下第一页的情况,从第一页开始,每翻一页就调用getData
方法处理该页的信息,并获得数据写入到文本。
补充一个下载百分比的方法:
#下载百分比
def downPercent(num, size, total): #数据块数量,大小,总的数据量
result = num*size/total
result = 1.0 if result > 1.0 else result #如果超过了1则下载完成了
if (result > 0.95): #下载到95以上输出下载状态
print("当前的进度:{}".format(result * 100))
合起来全部代码:
import requests
import re
import os
from bs4 import BeautifulSoup
from urllib.request import urlretrieve
'''
思路:获取网页信息---解析页面信息---模拟翻页---匹配所需数据---下载到本地文件
'''
baseurl = 'http://www.netbian.com/' #基本的url
#下载百分比
def downPercent(num, size, total): #数据块数量,大小,总的数据量
result = num*size/total
result = 1.0 if result > 1.0 else result #如果超过了1则下载完成了
if (result > 0.95): #下载到95以上输出下载状态
print("当前的进度:{}".format(result * 100))
'''
获取当前页面信息,从里面找到我们所需的数据
'''
def getHtml(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0'
}
html = requests.get(url, headers = headers, timeout=5) #伪装成浏览器访问
html.encoding = 'gbk'
return html
#code = re.findall(r'<meta charset="(.*?)" />', text)[0] #获取网页编码格式
#html.encoding = code #采用网页的编码格式是gbk
'''
模拟翻页
'''
def pageHtml():
nextHtml = baseurl+'/index_{}.htm' #下一页的格式
for page in range(1,11): #获取当前页的内容
getData(page, nextHtml.format(page) if page > 1 else baseurl ) #从第二页开始就采用下一页的格式
'''
获取网页信息
'''
def getData(page, url):
html1 = getHtml(url) #第一层网页 获取数据的
print(url)
soup = BeautifulSoup(html1.content, "html.parser")
_list = str(soup.find_all(class_="list")[0])
print(soup.find_all(class_="list"))
layer1 = r'href="/(.*?htm)"' #第一层网页的正则匹配,获取图片链接
datalist = re.findall(layer1, _list)
layer2 = r'<img src="(.*?)" alt='
jpglinks = []
for data in datalist:
html2 = getHtml(baseurl+data).text
jpg = re.findall(layer2, html2)[0]
jpglinks.append(jpg) #将图片链接放进列表内
downData(jpglinks, page, 1) #下载数据
'''
解析网页内容并下载
'''
def downData(jpglinks, page, num = 1):
newpath = r'.\彼岸桌面\页码{}'.format(page) #指定的文件夹
try:
t = os.makedirs(newpath) #创建新的文件夹
except FileExistsError :
print('文件夹存在,请删除文件夹 {} 再操作'.format(os.path.dirname(os.getcwd()+newpath.replace('.',''))))
else:
for jpg in jpglinks:
path = newpath+'\图片{}.jpg'.format(num)
urlretrieve(jpg, path ,downPercent) #下载到指定文件夹
num += 1
def main():
pageHtml()
if __name__ == '__main__':
main()
第三步: 构造多个线程下载
这样单线程下载比较慢,我测试了下下载10页的数据量,需要57s
:
于是我分了三个线程下载,时间减少到了23s
:
三个线程的速度并非是57s的三分之一,但是这速度比57s要快很多了,如果10个线程下载的话速度应该会更快。
多线程的话,只需要改动main
方法和pageHtml
即可:
def main():
start = time.time()
pages = [(1,4), (4,7), (7, 11)] #分成三个线程
threads = [] #存放多线程
for page in pages:
t = threading.Thread(target=pageHtml, args=(page[0], page[1], )) #创建三个线程
threads.append(t)
for thread in threads:
thread.start() #线程开始
for thread in threads:
thread.join() #线程结束
end = time.time()
print("用时:{}".format(end - start))
'''
模拟翻页
'''
def pageHtml(start, end):
nextHtml = baseurl+'/index_{}.htm' #下一页的格式
for page in range(start, end): #获取当前页的内容
getData(page, nextHtml.format(page) if page > 1 else baseurl ) #从第二页开始就采用下一页的格式
需要用到两个库:
import time
import threading
测试结果:
总结:
爬取一个简单的网页就是以上几个步骤,大部分网页都可以用以上套路去分析,爬取。
总的来说就是这么几个过程:分析网页信息看看自己需要哪些,解析网页信息,构造正则表达式获取信息,下载信息到本地。
感兴趣的可以尝试着爬取其他网站的壁纸,例如:ZOL桌面壁纸 壁纸族
附加:任意多个线程的
前面提到了三个线程的下载结果,我进一步修改了下,可以输入任意页码和任何的线程数进行下载了。 下载时长估计跟网速和cpu核数有关系。
import requests
import re
import os
import time
import threading
from bs4 import BeautifulSoup
from urllib.request import urlretrieve
'''
思路:获取网页信息---解析页面信息---匹配所需数据---下载到本地文件
'''
baseurl = 'http://www.netbian.com/' #基础的url
# 下载百分比
def downPercent(a, b, c):
result = a * b / c
result = 1.0 if result > 1.0 else result
if (result > 0.95): #下载到75以上输出下载状态
print("当前的进度:{}".format(result * 100))
def getHtml(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0'
}
html = requests.get(url, headers=headers, timeout=5) # 伪装成浏览器访问
html.encoding = 'gbk'
return html
# code = re.findall(r'<meta charset="(.*?)" />', text)[0] #获取网页编码格式
# html.encoding = code #采用网页的编码格式防止乱码存在
'''
模拟翻页
'''
def pageHtml(start, end):
nextHtml = baseurl + '/index_{}.htm' #下一页的格式
for page in range(start, end): #获取当前页的内容
getData(page, nextHtml.format(page) if page > 1 else baseurl) # 从第二页开始就采用下一页的格式
'''
获取网页信息
'''
def getData(page, url):
html1 = getHtml(url) #第一层网页 获取数据的
soup = BeautifulSoup(html1.content, "html.parser")
_list = str(soup.find_all(class_="list")[0])
layer1 = r'href="/(.*?htm)"' #第一层网页的正则匹配,获取图片链接
datalist = re.findall(layer1, _list)
layer2 = r'<img src="(.*?)" alt='
jpglinks = []
for data in datalist:
html2 = getHtml(baseurl + data).text
jpg = re.findall(layer2, html2)[0]
jpglinks.append(jpg) #将图片链接放进列表内
downData(jpglinks, page, 1) #下载数据
'''
解析网页内容并下载
'''
def downData(jpglinks, page, num=1):
newpath = r'.\彼岸桌面\页码{}'.format(page) #指定的文件夹
try:
t = os.makedirs(newpath) #创建新的文件夹
except FileExistsError:
print('文件夹存在,请删除文件夹 {} 再操作'.format(os.path.dirname(os.getcwd() + newpath.replace('.', ''))))
exit(0)
else:
for jpg in jpglinks:
path = newpath + '\图片{}.jpg'.format(num)
urlretrieve(jpg, path, downPercent) #下载到指定文件夹
num += 1
'''
根据页码和线程数量构建一个线程所需要下载的页码区间,用tuple的形式存放
'''
def thre_num(total, num):
pages = [x for x in range(1, total + 1)] #页码
point = round(total / num) #分割点,四舍五入计算
pages = pages[0: total + 1: point] #按照线程数量进行切分
if len(pages) - 1 != num: #如果分割的页码与线程数不匹配
pages.append(total) #那就表明最后一个线程未分配好,将总页码数加进去即可
pagenum = []
for x in range(0, len(pages) - 1): #防止list溢出,所以要减去1
'''因为循环range是左闭又开的关系,所以当判断到最后一页没计算到的时候,就单独处理最后一页并且+1处理'''
pagenum.append(tuple((pages[x], pages[x + 1] if x != len(pages) - 2 else pages[x + 1] + 1)))
return pagenum
'''
多个线程下载
'''
def main():
start = time.time()
total, num = 1, 2
MAXPAGE = int(re.findall(r'<a href=".*">(\d+)</a>', getHtml(baseurl).text)[0]) # 获取最大的页数
try:
while (total / num < 1 or total > MAXPAGE):
total = eval(input("输入要爬取到哪一页:"))
num = eval(input("线程数:"))
if total / num < 1:
print("页数必须大于线程数!")
elif total > MAXPAGE:
print("页数必须小于总页数{}".format(MAXPAGE))
except ZeroDivisionError: #除数为0了
print("线程数不能为0!")
main()
else:
pages = thre_num(total, num)
threads = [] #存放多线程
for page in pages:
t1 = threading.Thread(target=pageHtml, args=(page[0], page[1],))
threads.append(t1)
for thread in threads:
thread.start()
for thread in threads:
thread.join()
end = time.time()
print("{}页,{}个线程下载用时:{}".format(total, num, end - start))
if __name__ == '__main__':
main()
20页,10个线程下载结果:
40页,18个线程下载结果: