我们平时经常会有一些搜集数据的需要,尤其是图片数据。如果一个一个从网上找再下载下来实在是太麻烦了,这么繁琐的工作不如交给脚本去做。于是我写了一个简单的PYTHON3爬取百度图片的爬虫,github: https://github.com/plutojia/crawler-for-baiduImage
我们先打开百度图片看看它到底是什么样子的:
在其中搜索“美女”:
我们在浏览这些图片时可以发现,当你下拉滚动条时,又会有新的图片出现,而这些新的图片并不是一开始就加载好的,而是随着你的向下浏览不断请求刷新的,这就说明百度图片这种瀑布流式加载使用了Ajax技术,那么我们看看它的Ajax请求到底是什么样的。
在搜索美女的页面下,打开chrome浏览器的开发者工具,选择Network选项卡,再选择Network选项卡里的XHR选项卡,然后把网页向下翻,多翻几页,应该会看到请求出现,如图所示:
双击点开其中的任意请求看看:
发现请求url是https://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592&is=&fp=result&queryWord=%E7%BE%8E%E5%A5%B3&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=&st=-1&z=&ic=0&word=%E7%BE%8E%E5%A5%B3&s=&se=&tab=&width=&height=&face=0&istype=2&qc=&nc=1&fr=&expermode=&cg=girl&pn=120&rn=30&gsm=78&1537365676312=
前面的https://image.baidu.com/search/acjson?还挺清楚,后面的一长串是什么玩意啊?别急,继续往下看。在开发者工具中继续往下查看,可以看到有这么一段:
这里的Query String Parameters其实就是我们的请求信息,其中好多都是固定的,需要注意的只有几个:queryWord和word是你的搜索关键字,rn代表一页有多少图,一般取30,pn代表已经显示了多少图,取30*n即可。这样我们对请求的分析就搞定啦,写个代码试一下
首先先import一些常用模块
-
from urllib.parse
import urlencode
-
import requests
-
import re
-
import os
-
-
def get_page(offset):
-
params = {
-
'tn':
'resultjson_com',
-
'ipn':
'rj',
-
'ct':
'201326592',
-
'is':
'',
-
'fp':
'result',
-
'queryWord':
'帅哥',
-
'cl':
'2',
-
'lm':
'-1',
-
'ie':
'utf-8',
-
'oe':
'utf-8',
-
'adpicid':
'',
-
'st':
'-1',
-
'z':
'',
-
'ic':
'0',
-
'word':
'帅哥',
-
's':
'',
-
'se':
'',
-
'tab':
'',
-
'width':
'',
-
'height':
'',
-
'face':
'0',
-
'istype':
'2',
-
'qc':
'',
-
'nc':
'1',
-
'fr':
'',
-
'expermode':
'',
-
'cg':
'girl',
-
'pn': offset*
30,
-
'rn':
'30',
-
'gsm':
'1e',
-
'1537355234668':
'',
-
}
-
url =
'https://image.baidu.com/search/acjson?' + urlencode(params)
-
try:
-
response = requests.get(url)
-
if response.status_code ==
200:
-
return response.json()
-
except requests.ConnectionError
as e:
-
print(
'Error', e.args)
-
-
if __name__==
'__main__':
-
json = get_page(
1)
-
print(json)
-
运行以上代码,输出即为响应的JSON形式,若能成功输出,则请求正确。我们还需要从其中获得图片的地址,过程如下:
在开发者工具中,选择Preview,点开其中的data,发现一堆0,1,2,3....这些就代表了各个图片及其信息,我们点开一个看看,比如点开0:
这里面的objURL就是图片的真实地址了,而且是未经压缩的大图哦!等等,这地址怎么看着这么奇怪,这是因为百度对其进行了加密,解密只需要一个函数:
-
def baidtu_uncomplie(url):
-
res =
''
-
c = [
'_z2C$q',
'_z&e3B',
'AzdH3F']
-
d= {
'w':
'a',
'k':
'b',
'v':
'c',
'1':
'd',
'j':
'e',
'u':
'f',
'2':
'g',
'i':
'h',
't':
'i',
'3':
'j',
'h':
'k',
's':
'l',
'4':
'm',
'g':
'n',
'5':
'o',
'r':
'p',
'q':
'q',
'6':
'r',
'f':
's',
'p':
't',
'7':
'u',
'e':
'v',
'o':
'w',
'8':
'1',
'd':
'2',
'n':
'3',
'9':
'4',
'c':
'5',
'm':
'6',
'0':
'7',
'b':
'8',
'l':
'9',
'a':
'0',
'_z2C$q':
':',
'_z&e3B':
'.',
'AzdH3F':
'/'}
-
if(url==
None
or
'http'
in url):
-
return url
-
else:
-
j= url
-
for m
in c:
-
j=j.replace(m,d[m])
-
for char
in j:
-
if re.match(
'^[a-w\d]+$',char):
-
char = d[char]
-
res= res+char
-
return res
只需将objURL作为参数传进去就能返回图片真实地址。那么现在我们只需要写一个能分析响应从而得到objURL的函数,再将objURL变成真实地址,最后将图片下载下来就好了。已经没什么难的了,直接上完整代码:
-
from urllib.parse
import urlencode
-
import requests
-
import re
-
import os
-
save_dir=
'baidutu/'
-
-
def baidtu_uncomplie(url):
-
res =
''
-
c = [
'_z2C$q',
'_z&e3B',
'AzdH3F']
-
d= {
'w':
'a',
'k':
'b',
'v':
'c',
'1':
'd',
'j':
'e',
'u':
'f',
'2':
'g',
'i':
'h',
't':
'i',
'3':
'j',
'h':
'k',
's':
'l',
'4':
'm',
'g':
'n',
'5':
'o',
'r':
'p',
'q':
'q',
'6':
'r',
'f':
's',
'p':
't',
'7':
'u',
'e':
'v',
'o':
'w',
'8':
'1',
'd':
'2',
'n':
'3',
'9':
'4',
'c':
'5',
'm':
'6',
'0':
'7',
'b':
'8',
'l':
'9',
'a':
'0',
'_z2C$q':
':',
'_z&e3B':
'.',
'AzdH3F':
'/'}
-
if(url==
None
or
'http'
in url):
-
return url
-
else:
-
j= url
-
for m
in c:
-
j=j.replace(m,d[m])
-
for char
in j:
-
if re.match(
'^[a-w\d]+$',char):
-
char = d[char]
-
res= res+char
-
return res
-
-
def get_page(offset):
-
params = {
-
'tn':
'resultjson_com',
-
'ipn':
'rj',
-
'ct':
'201326592',
-
'is':
'',
-
'fp':
'result',
-
'queryWord':
'帅哥',
-
'cl':
'2',
-
'lm':
'-1',
-
'ie':
'utf-8',
-
'oe':
'utf-8',
-
'adpicid':
'',
-
'st':
'-1',
-
'z':
'',
-
'ic':
'0',
-
'word':
'帅哥',
-
's':
'',
-
'se':
'',
-
'tab':
'',
-
'width':
'',
-
'height':
'',
-
'face':
'0',
-
'istype':
'2',
-
'qc':
'',
-
'nc':
'1',
-
'fr':
'',
-
'expermode':
'',
-
'pn': offset*
30,
-
'rn':
'30',
-
'gsm':
'1e',
-
'1537355234668':
'',
-
}
-
url =
'https://image.baidu.com/search/acjson?' + urlencode(params)
-
try:
-
response = requests.get(url)
-
if response.status_code ==
200:
-
return response.json()
-
except requests.ConnectionError
as e:
-
print(
'Error', e.args)
-
-
def get_images(json):
-
if json.get(
'data'):
-
for item
in json.get(
'data'):
-
if item.get(
'fromPageTitle'):
-
title = item.get(
'fromPageTitle')
-
else:
-
title=
'noTitle'
-
image = baidtu_uncomplie(item.get(
'objURL'))
-
if(image):
-
yield {
-
'image': image,
-
'title': title
-
}
-
-
def save_image(item,count):
-
try:
-
response = requests.get(item.get(
'image'))
-
if response.status_code ==
200:
-
file_path = save_dir+
'{0}.{1}'.format(str(count),
'jpg')
-
if
not os.path.exists(file_path):
-
with open(file_path,
'wb')
as f:
-
f.write(response.content)
-
else:
-
print(
'Already Downloaded', file_path)
-
except requests.ConnectionError:
-
print(
'Failed to Save Image')
-
-
def main(pageIndex,count):
-
json = get_page(pageIndex)
-
for image
in get_images(json):
-
save_image(image, count)
-
count +=
1
-
return count
-
if __name__==
'__main__':
-
if
not os.path.exists(save_dir):
-
os.mkdir(save_dir)
-
count=
1
-
for i
in range(
1,
20):
-
count=main(i,count)
-
print(
'total:',count)
最后爬下来的图片会以1,2,3,4~命名,最终输出图片总数。