爬虫-煎蛋网图片下载和分类

一. 通过爬取煎蛋网随手拍,下载网页上的图片。

1.煎蛋网不知道是什么原因把以前妹子图的地址改成随手拍,而且他下面的页码在超过一定页数(目测35页左右),就会删除掉前面几页的内容。他页面地址http://jandan.net/ooxx/page-32#comments, 32就是他的页码数。我们首先需要知道他首页的页码,然后后面-=1,直到1。

Chrome浏览器在随手拍页面首页审查元素,看到current-comment-page就是首页第32页的代码位置,所以知道我们找到这个标签就可以确定首页的页码。我使用了BeautifulSoup,得到每个页面的文本soup数据函数data(url)就可以查找到该标签。函数getpage(soup)代码第40行soup.find_all('span', attrs={'class':'current-comment-page'}), 随后再通过函数getpage(soup)其他页面的地址就很容易了。

2.得到页面地址后,我们就需要知道每个页面上所有图片的地址。

任意一张页面上的图片鼠标右键审查元素可以看到标签的组成,可以看到原图地址就是把mw600改为large。所以我们只要找到这个标签就可以了。函数downpic(soup)第57行soup.find_all('a', attrs={'class':'view_img_link'})

其实在我刚写这段代码时,煎蛋网的还采用了反爬机制,对地址进行了加密,所以我代码中还保留的那部分内容作为备注。参考前辈的例子Python爬虫爬取煎蛋网无聊图,煎蛋网也是用心良苦啊,栏目名字都改来来去的。

3.然后就可以读取图片地址的数据,下载图片。下载图片实际上就是把图片的数据写入一个新的图片文件里。函数downpic(soup)第78,79行。

4.完善代码。以及其中遇到的问题。

  • 检测网页的编码。自然用到chardet.但是有时会出现不名的原因导致检测的编码类型有误,导致页面有乱码,查找不到我们想要的标签。本来是utf-8, 得到的结果是windows-1254,这是什么鬼,我也不知道,查了很久终于在一篇文章中找到了解决方法,见下面应用内容。参考再也不用担心网页编码的坑了, 把response.encoding = None, 运行一下再改过来 = code.问题就解决了。这里还要注意,chardet.detect的参数是二进制数据,response.text是文本数据,.content才是二进制数据。

    1
    2
    3
    code = chardet.detect(response.content)['encoding']
    response.encoding = code
    html = response.text

    那么当你发现response.text返回乱码的时候,怎么办呢。。。

    只要先设置编码为None…

    再打印.text就可以了..

    1
    2
    3
    > response.encoding = None
    > response.text
    >
  • 为了不重复下载以前下载过的图片(毕竟每天图片都在更新), 所以我使用pickle把所有的图片名保存在了文件里,再下载时,先读取保存的文件(一个列表以二进制的数据保存),列表中没有的图片名才下载。由于网站保存的图片名都是固定的,所以当遇到旧的图片名时就可以停止下载。如果把每次出现的新图片都储存在文件里,势必让文件越来越大,为了解决这个问题,我把新图片名写入一个临时文件,再写入一个旧的图片名,最后删除原来的文件,把临时文件改名为正式文件。这样每次下载完后只保存了新的图片名和一个旧图片名作为下载的终点。

  • 同时为了让函数downpic(soup)读取到旧图片名时让函数和主程序main()都立刻停止循环执行下面的脚本不要再重复读取页面数据,就需要从函数中发出一个信号,让程序终止。我使用了raise语句来引发一个异常,我自定义了一个Stopdownload()的异常, 函数和主程序都可以通过接受到异常来终止程序。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import requests
import re
from bs4 import BeautifulSoup
import chardet
import urllib.parse as up
import base64
import os.path
import os
import time
import pickle

#自定义一个异常类
class Stopdownload(Exception):
def __init__(self, err='遇到旧图片,停止下载了。'):
Exception.__init__(self, err)


#定义函数返回地址二进制数据内容
def bhtml(url):
agent = 'User-Agent'
agentvalue = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36'
headers = {agent:agentvalue}
#proxy = {'http':'120.78.174.170:8080'} , proxies=proxy
response = requests.get(url=url, headers=headers)
bhtml = response.content
return bhtml

#每个地址的soup数据
def data(url):
agent = 'User-Agent'
agentvalue = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36'
headers = {agent:agentvalue}
#proxy = {'http':'120.78.174.170:8080'} , proxies=proxy

response = requests.get(url=url, headers=headers)
global code
code = chardet.detect(response.content)['encoding']
response.encoding = code
html = response.text
soup = BeautifulSoup(html, 'html.parser')
return soup


#所有分页的地址(生成器)
def getpage(soup):
list1 = soup.find_all('span', attrs={'class':'current-comment-page'}) # <span class="current-comment-page">[56]</span>
a = list1[0].text #a='[56]'他是一个字符串
page = int(a[1:3]) #切片

for n in range(page-1, 0, -1):#页面倒数到1
pageurl = ''.join(['http://jandan.net/ooxx/page-', str(n), '#comments'])
yield pageurl

#每个页面的图片地址,下载保存到本地,
def downpic(soup):
try: #打开保存的已下载的图片名数据
with open('D:\download\piclist.txt', 'rb') as f2:
list1 = pickle.load(f2)#把数据导入列表1
except: #如果第一次下载,列表1为空列表
list1 = []
#找到包含有页面图片下载地址的标签列表2
#list2 = soup.find_all('span', attrs={'class':'img-hash'}) #网站使用了反爬机制,参考https://www.jianshu.com/p/5351baf254ef
list2 = soup.find_all('a', attrs={'class':'view_img_link'})
#定义一个空的列表3,用于写入所有新下载的图片。
list3 = []
#找到所有图片的下载地址和图片名称
for eachcode in list2:
#piccode = str(base64.b64decode(eachcode.string.encode(code)))[2:].replace('mw600', 'large').replace('\'', '')#使用反爬机制时,需要对数据解密。参考https://www.jianshu.com/p/5351baf254ef
piccode = eachcode['href']
picurl = ''.join(['http:', piccode])
if picurl in ['http://ww3.sinaimg.cn/large/006XNEY7gy1g1xabxlnq4j30fo0jwwhv.jpg']:#如果网页的图片出现的加载错误,导致不能读取picdata。就需要跳过这个图片。其实也可以使用异常处理的方法
continue
picname = os.path.split(picurl)[1]
#导出图片的二进制数据
picdata = bhtml(picurl)
#如果图片没有下载过,则下载图片, 并把图片名导入列表3
if picname not in list1:
print(picname)
list3.append(picname)
with open(r'D:\download\temp.txt', 'ab') as f3:#把列表3的数据保存在临时文件(包括新下载的图片名)
pickle.dump(list3, f3)

if 'gif' not in picname:#不下载gif图片
with open(picname, 'wb') as f:
f.write(picdata)
time.sleep(0.5)
else:#如果有下载过
print(picname, '是旧图片')
list3.append(picname)
with open(r'D:\download\temp.txt', 'ab') as f3:
pickle.dump(list3, f3)
raise Stopdownload() #抛出异常

def main():
try:#新建一个文件夹,如果已经存在就pass
os.mkdir('D:\download\pic')
except:
pass
os.chdir(r'D:\download\pic') #改变工作目录
mainurl = 'http://jandan.net/ooxx' #主页
print(mainurl)
mainsoup = data(mainurl)#导出主页的soup
try:
downpic(mainsoup)#下载主页的图片,如果全部是新图片不会触发异常
except Stopdownload as e:#如果遇到旧图片则停止下载
print(e)
pass
else:#如果没有遇到旧图片
pageurl = getpage(mainsoup)#下面页码的地址
for x in pageurl:
print(x)
pagesoup = data(x)#每个页面的soup
try:
downpic(pagesoup) #下载每个页面的图片
except Stopdownload as e:#如果遇到旧图片立刻停止循环
print(e)
break
#删除旧的文件数据,把临时文件改为正式文件,储存了最新下载的图片名数据

os.remove(r'D:\download\piclist.txt')
os.rename(r'D:\download\temp.txt', r'D:\download\piclist.txt')

#为了是在下载图片时遇到旧图片时停止下载,而不用继续循环读取旧页面的数据,
#我在下载图片的函数中使用raise语句,当出现旧图片时立刻引发异常。
#在主函数中就可以捕获这个异常而中断循环。

if __name__ == '__main__':
main()

二.对已下载图片进行分类

虽然自动下载了图片,但是是否是自己喜欢的,还要自己去判断(没有非人工智能高大上),这段代码就是实现了自动显示图片,自己再选择是否喜欢,并把图片保存在不同的文件夹里。使用了matplotlib.pyplot, PIL/Image, shutil, easygui这四个库。

在写这段代码时遇到os.chdir('C:\Users\vulcanten\Desktop\test')出现异常,原因是\在字符串中是当作转义字符来使用, 所以出现了报错,我们只要在使用路径名时在前面加上r, 就可以解决了 os.chdir(r'C:\Users\vulcanten\Desktop\test'), 还可以使用\\, 或者/来代替。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#对下载的图片进行分类
import matplotlib.pyplot as plt
from PIL import Image
import os
import time
import shutil
import easygui as eg
import pickle

os.chdir(r'D:\download\pic')
ways = os.walk(r'D:\download\pic')

for each in ways:
list1 = each[2]
for picname in list1:
img = Image.open(picname) #打开图片数据
print(picname)
plt.ion()#自动滚动显示图片并执行相关脚本, 需要与pause()一起使用
plt.imshow(img)#显示图片
plt.show()
plt.pause(1)#暂停等候
#使用easygui来对图片进行判断,并移动图片到对应文件夹
like = eg.buttonbox('喜欢的点OK', '', ('OK', '不喜欢'))
if like == 'OK':
shutil.move(picname, ''.join(['D:/download/like/', picname]))
elif like == '不喜欢':
shutil.move(picname, ''.join(['D:/download/unlike/', picname]))
else:
break

plt.close()

上图看一下效果

运行后发现这个代码运行速度比较慢,还没有使用图片查看器再结合键盘的删除键来的快。虽然如此,但代码还是提供了一些有用的思路,以备今后使用。

本文离不开以下文章的贡献:

python 自定义异常和异常捕捉

创建自定义异常

python之文件操作-复制、剪切、删除等

python 图像的显示关闭以及保存

python中使用PIL和matplotlib.pyplot打开显示关闭暂停和保存图片

总结:

  1. 在函数中使用raise来引起异常,实现终止程序的功能。

  2. 自定义异常的方法

  3. 用shutil在移动或者复制文件

  4. 实现滚动显示图片的功能

  5. 写爬虫最难的是遇到反爬机制,但是自己又看不懂Jason文件。