* Y, P2 {! i: V: V% m' A, \前言
- d$ h% A: Y% K- }
4 B; ]& ^* P9 L2 r$ z+ h4 A7 [今日我们在 今日头条 网站上爬取 街拍美图 。 _8 ]7 [1 v9 u+ K$ z f7 O% P
今日头条 的数据都是用 Ajax 技术加载渲染完成,打开 今日头条 页面源码,连一根鸡毛都没有。
1 I/ t3 T2 }4 | }, k6 M$ o2 b4 m" v
7 X* I e) J2 N. a" P I% G& B( \2 ?' u3 X8 t( D) N+ x+ @
/ n8 ~. p1 Y9 e
假如今天生活欺骗了你,不要悲伤,不要哭泣,因为明天生活还会继续欺骗你。
" i7 a3 z f2 k3 |) q" m! S7 C1 m7 a: Z; h7 m% `2 z8 b7 t
3 }' z Y, h( Y, f. v% [! h1 `
在我们爬虫界,按照 ‘可见即可爬’ 的原则, 所谓的 Ajax 就是 ’换一个页面‘ 爬取我们想要爬取的资源。; ~/ C/ V: W f" p+ V* f2 m
换的那个页面,有时是 XHR 文件,有时是 HTML 文件。2 j# g# m* m- r/ r* l9 S% M6 J
目标站点分析
+ }% `9 f# c" t6 |% LF12审查页面元素。8 s7 ~8 v$ I; {! N5 \+ \
- h/ s- ?; Q) Z: [2 V
- D1 h, ~4 D. o9 K; D0 {+ y" z8 n# t2 C1 \& `7 ?0 O
2 U! Y2 ]6 L- a; J
我们需要的资源全部在这个 URL 下。& @+ ^7 b" v; ~
获取 JSON 数据
3 K$ y) Z0 t8 u& @6 K+ A2 ^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()$ T. Z8 j' N# Q$ [
转换映射对象或两个元素的元组,其可以包含的序列 str 或 bytes 对象。如若是字符串,则结果是由 ‘&’ 分隔的 key=value 的系列队。 解析 Json 数据* [/ h/ x Y5 K8 ]4 @) J6 u; e$ r
2 o: t2 ?1 s4 fdef getImage(json):( a1 O1 N5 v! Z/ J' u7 C
+ Q1 ~$ M7 }; d data = json.get('data')/ _# L, r6 E1 W# ^& O/ x1 |5 v. O
3 W/ r- P) @2 S- w6 ` for item in data:0 H+ l' O. }8 D7 E
6 u! e7 W% d5 W/ `8 p9 o0 Q
title = item.get('title')
: z5 v* L. S- H. [) s8 i
4 T$ b3 R5 q5 H* e. F+ F; {2 |* L image_list = item.get('image_list')
+ o. y( b, B O; ]+ {7 h* `7 u: J- A# ]% s" J
if image_list:
0 w' C: S! k$ q0 M) C( |* V$ E' P1 \1 \# w M7 o- H8 C
for item in image_list:
$ ^7 X/ X2 v( x0 Z: w4 ~; D$ b2 B- J/ F q- g7 k$ ?7 h
yield{; ~- n0 D% }5 h' S3 R) `
( R3 l* U; s. o2 k `/ [' _- o3 ?
'title': title,
4 U2 M8 q+ A$ a G8 l* U# @% |% F3 H
'image': item.get('url')
! o2 h0 N# N5 C" `6 Q- u- Z. _0 o7 t0 F' z& o
}
j4 L3 @$ T$ C: y2 d/ A" V- h. c5 {保存图片
' E5 |: S9 S, s- H( d9 ^% |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() 函数的一段话:
2 y" g/ \0 r1 R. ?4 h: n$ h3 wAt 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. 大概意思是:
$ `; a0 ]8 Z1 R到目前为止,hexdigest() 和 digest() 函数能满足你把一串字符串的组合物变成一段摘要的需要。 官方文档:2 v& [6 H- C M! B6 p
https://docs.python.org/2/library/hashlib.html 保存到 MongDB3 u" C+ Z, w9 @# x! g# M9 U: Y
def saveToMongo(item): if db[MONGO_TABLE].insert(item): print('储存到MONGODB成功', item) return False主函数和进程池 {! o5 v, F7 v) O( ^
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 了。 p1 `+ O/ F$ d& l* c2 x6 i
总结# Z+ ?8 I! H% _* L5 F8 x
主要关注如何下载和解析 Json数据。
" O/ O* U/ N& m4 _- C' Q公众号内输出 ‘崔佬视频’获取崔庆才大佬的《Python3WebSpider》全套视频。
q0 }3 j6 I: g; \全码
* y& S/ X/ u+ Timport 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 K m" Y# z# j5 F! S
|