: ?+ E! ^% ]2 H. K9 x& ?( A; C
前言
) ]: V& p3 q' J% q, x/ o" n5 a3 B& f1 z" W( F1 ^" ~
今日我们在 今日头条 网站上爬取 街拍美图 。7 u0 p" ^8 j6 i' {1 b
今日头条 的数据都是用 Ajax 技术加载渲染完成,打开 今日头条 页面源码,连一根鸡毛都没有。! b V# o; Z* C) t- D2 {
$ `7 Z( |7 J ~9 g& s. v) P
. J5 j. h) e+ V. M4 ]( c0 M/ \- S, q3 N: d
2 O3 _4 H) [3 Q1 H
假如今天生活欺骗了你,不要悲伤,不要哭泣,因为明天生活还会继续欺骗你。$ h% `5 o( w5 \$ v6 _
% W% D2 w0 H, e: r6 @1 M5 ^
9 l. {8 @5 M. ^4 P. _ _' b在我们爬虫界,按照 ‘可见即可爬’ 的原则, 所谓的 Ajax 就是 ’换一个页面‘ 爬取我们想要爬取的资源。
# Y+ ]5 j, s) a; N换的那个页面,有时是 XHR 文件,有时是 HTML 文件。
) y, Z- \' n5 ~ [2 l6 [3 l# l9 r目标站点分析6 M. y( U& b: N
F12审查页面元素。& Y9 ~& @- U* K1 I0 Z! ~
1 ^8 }7 N( a! i! _( D& X/ E
9 N& ?0 `* `3 Q% P
% c7 }8 r3 H6 F0 T3 a6 l
2 h8 K8 J; T. ?6 b( A: L
我们需要的资源全部在这个 URL 下。( K3 R# A1 ?0 Z5 u0 R- b; ^
获取 JSON 数据
, I8 u8 m& x8 c0 f2 D5 ~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()+ ~$ a% o1 ?0 V. i! h
转换映射对象或两个元素的元组,其可以包含的序列 str 或 bytes 对象。如若是字符串,则结果是由 ‘&’ 分隔的 key=value 的系列队。 解析 Json 数据
" p4 q4 g( y1 ~+ M/ |5 i9 T
( H6 l; q6 x% f. P8 k4 M& idef getImage(json):" A' |: d+ G' S, I* }5 L
# L8 e% K) E: e8 o6 L" l0 V5 } data = json.get('data')
# K! X7 j! F% \- g% I
. h/ A; ~% b" N7 W for item in data:
' S; l+ X, f/ b( [# ^+ J& i+ D' H* K
title = item.get('title')+ d6 S; P8 I' o- U
1 S2 V L- m6 h& y* v. l$ Z
image_list = item.get('image_list')9 o' u7 _4 Z- a2 a& K0 e- v
( T- {- A1 Y& y" u, V% v2 o: @
if image_list:
6 R# W& g- N" u5 ~
' Y' v8 ~. G1 L9 E$ k, u3 Y; R$ A for item in image_list:
# ]3 n( l6 n! d
6 q" W$ |1 ~4 R3 D yield{
* g2 O4 Y) j7 P$ t' B' O+ D, B0 A3 k: ~! N+ o: P4 n& B
'title': title,( S* y8 r( Q/ c
, m5 y. q- F! X/ x7 [
'image': item.get('url'), O: H5 X/ i+ J
$ h* c: d3 B- N& E% l, K9 a
}
( H" }, _1 b& D+ N7 A保存图片
0 n6 X7 F% _: {* {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() 函数的一段话:
6 }: h' ^. [6 @6 ~At 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. 大概意思是:! s* D" m- I8 G3 s- Q
到目前为止,hexdigest() 和 digest() 函数能满足你把一串字符串的组合物变成一段摘要的需要。 官方文档:
- b+ Q$ y# A4 r$ l8 shttps://docs.python.org/2/library/hashlib.html 保存到 MongDB
+ T4 P# y* N$ F, {; Hdef saveToMongo(item): if db[MONGO_TABLE].insert(item): print('储存到MONGODB成功', item) return False主函数和进程池' @: h5 Q. @4 F# C$ g
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 了。7 ?! t# |+ U# Q7 `
总结
4 ^+ T9 m. O# e' t* h0 Z主要关注如何下载和解析 Json数据。/ S3 N7 G+ v) ^2 N4 T" X
公众号内输出 ‘崔佬视频’获取崔庆才大佬的《Python3WebSpider》全套视频。% L$ n- j/ W d0 i# T2 [
全码- \$ m+ r( A& J
import 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() #主进程阻塞等待子进程的退出
+ c( Y( a# u7 r4 K3 j0 W7 @: f4 g |