( F; x6 {' v( S, c
前言
/ s1 O6 b: R1 `; d) R
: Z3 C) h; e) L今日我们在 今日头条 网站上爬取 街拍美图 。* L, F- P0 [# q4 k' k
今日头条 的数据都是用 Ajax 技术加载渲染完成,打开 今日头条 页面源码,连一根鸡毛都没有。
/ _( |" r1 T3 r$ M; f' Q+ B2 p7 V" w3 `* P! A
3 v7 [7 m T e8 X7 s! C6 r) ~
! y6 C `, P4 |1 {9 c" i3 @
! p. D- t& Z n4 V' E: S# m* Y假如今天生活欺骗了你,不要悲伤,不要哭泣,因为明天生活还会继续欺骗你。% Z/ L# j9 V j r; l( d8 M; D
$ r# \- [' U% m) t$ {- X
2 A3 Q) N; l& M" y在我们爬虫界,按照 ‘可见即可爬’ 的原则, 所谓的 Ajax 就是 ’换一个页面‘ 爬取我们想要爬取的资源。: g( B) r5 S+ c4 @% ~" v7 d
换的那个页面,有时是 XHR 文件,有时是 HTML 文件。
3 u; |: `% y/ l: A4 b' s% L9 h1 n目标站点分析% n& \, N# i$ j* C* z& j& L
F12审查页面元素。' v& g( K, a* a" o' \& _9 v& {- n
! C+ m/ ]0 F x: ]3 ] Z
% C! `, i( z8 z9 H) L( I5 s
! q/ }; {& w; N8 |$ F1 f- K$ l8 f" [$ P* }$ B! H% E( K
我们需要的资源全部在这个 URL 下。
" l& N4 P z/ a) J7 |( C9 O获取 JSON 数据' g% W! M) I2 I2 t7 g( c0 }
def getPage(offset): params = { 'offset': offset, 'format': 'json', 'keyword': '街拍', 'autoload': 'true', 'count': '20', 'cur_tab': '3', 'from': 'gallery', } url = 'https://www.toutiao.com/search_content/?' + urlencode(params) try: r = requests.get(url) if r.status_code == 200: return r.json() except requests.ConnectionError: return ""urllib.parse.urlencode()- r- H, M6 a1 Y" v- g" V
转换映射对象或两个元素的元组,其可以包含的序列 str 或 bytes 对象。如若是字符串,则结果是由 ‘&’ 分隔的 key=value 的系列队。 解析 Json 数据
6 J1 E# L- B$ k" `$ ~3 m6 q/ j2 b; j; @7 C. A" r
def getImage(json):3 D' ~- l& z. w( h! f5 o4 G
5 d# V5 s1 m( [) m
data = json.get('data')$ J% `: W: s. ~
$ m$ M. h5 ~( b) K' V/ U for item in data:* ~! ?: Y$ _3 s! ?
; z3 }) t9 d- {) S2 ~4 j2 _6 m title = item.get('title')
: _2 j% E, _, Z$ ^4 r( r3 z! C5 h* \
image_list = item.get('image_list')) _. F% I. ]0 _, a @+ K
& u' j1 D2 k( q0 C
if image_list:
; S8 E4 e1 n+ i* P
3 |& o2 |0 |5 x for item in image_list:
" J1 i, N5 {" a# I6 a# ^
r9 u$ b$ q) T, o Z! \, N yield{5 C& i. r5 {4 g# _$ |" z; K
( T7 E# v, z) t1 k0 O2 u
'title': title,
( B7 s3 A: I |* L. C3 V; f# O
$ g7 p7 ^3 O9 h; c* e 'image': item.get('url')
7 O9 q. o( v# I, W% M Y! Z9 u: \* ], C7 L% [: \
}
8 ?, ]* n0 L! R' D% i0 u" T$ \保存图片' j H9 a m% ]# J+ W
def saveImage(item): img_path = 'img' + os.path.sep + item.get('title') if not os.path.exists(img_path): os.makedirs(img_path) local_image_url = item.get('image') new_image_url = local_image_url.replace('list', 'large') r = requests.get('http:' + new_image_url) if r.status_code == 200: file_path = img_path + os.path.sep +'{0}.{1}'.format(md5(r.content).hexdigest(), 'jpg') if not os.path.exists(file_path): with open(file_path, 'wb') as f: f.write(r.content)在官方文档中描述 hexdigest() 函数的一段话:
+ b! U0 G4 W& PAt any point you can ask it for the digest of the concatenation of the strings fed to it so far using the digest() or hexdigest() methods. 大概意思是:3 g0 {; N1 P0 P6 n( n
到目前为止,hexdigest() 和 digest() 函数能满足你把一串字符串的组合物变成一段摘要的需要。 官方文档:
2 w# i# N# c$ R: ]2 }' s# _$ qhttps://docs.python.org/2/library/hashlib.html 保存到 MongDB
3 }7 _- a1 x% X, D4 Gdef saveToMongo(item): if db[MONGO_TABLE].insert(item): print('储存到MONGODB成功', item) return False主函数和进程池- P' R8 y. n: |+ C5 K
def main(offset): json = getPage(offset) for item in getImage(json): saveImage(item) saveToMongo(item)if __name__ == '__main__': pool = Pool() groups = [x * 20 for x in range(2)] #爬取五页 pool.map(main, groups) pool.close() #关闭进程池(pool),使其不在接受新的任务 pool.join() #主进程阻塞等待子进程的退出对 pool 对象调用 join() 方法会让主进程等待所有子进程自行完毕,调用 join() 之前必须先调用 close() ,让其不再接受新的 Process 了。
; T- G8 f, _) ?9 X! q" ~5 {. P- _总结& ^; j, v$ |8 k
主要关注如何下载和解析 Json数据。* l& X, |2 I) A7 r: c0 {
公众号内输出 ‘崔佬视频’获取崔庆才大佬的《Python3WebSpider》全套视频。
# Q. x% D5 ~8 H' G; O$ l全码
7 T4 T* p9 E/ j& }3 Yimport requestsfrom urllib.parse import urlencodeimport osfrom hashlib import md5import pymongofrom multiprocessing.pool import PoolMONGO_URL = 'localhost'MONGO_DB = 'toutiao'MONGO_TABLE = 'toutiao' #数据集合Collectionclient = pymongo.MongoClient(MONGO_URL) # MongoClient 对象,并且指定连接的 URL 地址db = client[MONGO_DB] #要创建的数据库名def getPage(offset): params = { 'offset': offset, 'format': 'json', 'keyword': '街拍', 'autoload': 'true', 'count': '20', 'cur_tab': '3', 'from': 'gallery', } url = 'https://www.toutiao.com/search_content/?' + urlencode(params) try: r = requests.get(url) if r.status_code == 200: return r.json() except requests.ConnectionError: return ""def getImage(json): data = json.get('data') for item in data: title = item.get('title') image_list = item.get('image_list') if image_list: for item in image_list: yield{ 'title': title, 'image': item.get('url') }def saveImage(item): img_path = 'img' + os.path.sep + item.get('title') if not os.path.exists(img_path): os.makedirs(img_path) local_image_url = item.get('image') new_image_url = local_image_url.replace('list', 'large') r = requests.get('http:' + new_image_url) if r.status_code == 200: file_path = img_path + os.path.sep +'{0}.{1}'.format(md5(r.content).hexdigest(), 'jpg') if not os.path.exists(file_path): with open(file_path, 'wb') as f: f.write(r.content)def saveToMongo(item): if db[MONGO_TABLE].insert(item): print('储存到MONGODB成功', item) return Falsedef main(offset): json = getPage(offset) for item in getImage(json): saveImage(item) saveToMongo(item)if __name__ == '__main__': pool = Pool() groups = [x * 20 for x in range(2)] #爬取五页 pool.map(main, groups) pool.close() #关闭进程池(pool),使其不在接受新的任务 pool.join() #主进程阻塞等待子进程的退出5 h a- }7 K' |- b2 _% O
|