1 import requests 2 from bs4 import BeautifulSoup 3 import json,re,os 4 from urllib.parse import urlencode 5 from hashlib import md5 6 from multiprocessing.pool import Pool 7 from requests.exceptions import RequestException 8 import pymongo 9 #引入模块config中所有变量 10 from config import * 11 from json.decoder import JSONDecodeError 12 13 #声明MongoDB对象 14 client = pymongo.MongoClient(MONGO_URL,connect=False) 15 db = client[MONGO_DB] 16 #这里插入到MongoDB。 17 #保存到本地 18 def save_to_mongo(result): 19 if db[MONGO_TABLE].insert(result): 20 print('存储到MongoDB成功',result) 21 return True 22 else: 23 return False 24 #获取索引页数据 25 def get_page_index(offset,keyword): 26 data = { 27 'offset': offset, 28 'format': 'json', 29 'keyword': keyword, 30 'autoload': 'true', 31 'count': '20', 32 'cur_tab': '3', 33 'from':'gallery' 34 } 35 #将data变成请求参数 36 url = 'https://www.toutiao.com/search_content/?'+urlencode(data) 37 try: 38 response = requests.get(url) 39 response.raise_for_status() 40 response.encoding = response.apparent_encoding 41 return response.text 42 except RequestException: 43 print('爬取索引页失败!') 44 #解析索引页 45 def parse_page_index(html): 46 # 获取所有详情页的url 47 try: 48 # 页面是json格式的,装换成字符串格式 49 data = json.loads(html) 50 # data.keys()返回所有键名 51 if data and 'data' in data.keys(): 52 for item in data.get('data'): 53 if item.get('cell_type') is not None: 54 continue 55 yield item.get('article_url') 56 except JSONDecodeError: 57 pass 58 59 def get_page_detail(url): 60 # 请求详情页的url 61 62 #这里不加 headers 是获取不到数据的。 63 headers = {'user-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 '} 64 try: 65 response = requests.get(url,headers=headers) 66 response.raise_for_status() 67 response.encoding = response.apparent_encoding 68 return response.text 69 except RequestException: 70 print('爬取详情页失败!',url) 71 return None 72 73 def parse_page_detail(html,url): 74 '''获取详情页的标题和图片地址url''' 75 76 #利用BeautifulSoup库提取标题 77 try: 78 soup = BeautifulSoup(html,'lxml') 79 title = soup.select('title')[0].get_text() 80 if title: print(title) 81 except: 82 pass 83 84 # 利用正则表达式提取图片地址 85 images_pattern = re.compile(r'.*?gallery: JSON.parse("(.*?)")',re.S|re.M) 86 result = re.search(images_pattern,html) 87 88 if result: 89 data = json.loads(result.group(1).replace('\','')) 90 #print(data) 91 if data and 'sub_images' in data.keys(): 92 sub_images = data.get('sub_images') 93 #提取图片 94 images = [item.get('url') for item in sub_images] 95 #保存图片到本地 96 for image in images: 97 download_image(title,image) 98 return {'title':title, 99 'url':url, 100 'images':images} 101 else: 102 print('空') 103 104 def save_image(title,result): 105 img_path = 'image' + os.path.sep + title 106 if not os.path.exists(img_path): 107 os.makedirs(img_path) 108 file_path = '{0}/{1}.{2}'.format(img_path,md5(result).hexdigest(),'jpg') 109 if not os.path.exists(file_path): 110 with open(file_path,'wb') as f: 111 f.write(result) 112 print("%s下载完成"%file_path) 113 else: 114 print("%s已经存在"%file_path) 115 116 def download_image(title,url): 117 try: 118 print('正在下载',url) 119 r = requests.get(url) 120 r.raise_for_status() 121 r.encoding=r.apparent_encoding 122 save_image(title,r.content) 123 124 except RequestException: 125 print('请求图片出错',url) 126 return False 127 128 def main(offset): 129 #调用函数 130 html= get_page_index(offset,KEYWORD) 131 for url in parse_page_index(html): 132 html = get_page_detail(url) 133 if html: 134 result = parse_page_detail(html,url) 135 # print(result) 136 if result: save_to_mongo(result) 137 138 139 140 if __name__=='__main__': 141 #开启多线程抓取 142 pool = Pool() 143 group = [x*20 for x in range(GROUP_START,GROUP_END+1)] 144 pool.map(main,group) 145 pool.close()
以上是spider.py
借鉴:https://blog.csdn.net/sixkery/article/details/81836017
有两个坑我遇到的:
第一个坑
如果你点击图集,打开开发者工具,刷新一下,你会发现,你的页面在综合这一栏。
你会发现你找到的上图的参数跟我的不一样。
这里你可以刷新之后点击图集,然后向下拖动几个,让页面多加载一些。
其中「cur_tab:3」这个参数中的数字对应图集,这下你明白了吧。
到这里就好办了,我们点击 Preview
可以看到 data 下方有 article_url 当然还有 image_url ,你点击 image_url 你会发现只有四个 url ,复制链接在浏览器上打开你发现 TMD 还不是大图,还是缩略图,所以我们不用它,我们获取 article_url ,获取之后再次请求不就完了吗。
分析详情页
这里我们来看看详情页的内容
详情页分析
这里我们随便点开一个组图的 url 来分析,我们可以看到返回的数据是一大堆 html ,这里有必要说一下,我们在获取页面内容的时候,一般浏览器会返回给我们的是 response 里的内容。但是我们大多数爬取数据,用 xpath 、BeautifulSoup 获取数据,看的是 Elements 里的内容。这里一定要看看 response 里的内容和 Element 里的是否相同。
这里的图片地址还真是不好找,具体怎么找呢,点开图片的地址,复制下链接,在HTML里「Ctrl + F」就发现了。是在红色框里面的。
第二个坑
首先,这里获取的页面内容是 json 格式的,我们看一下这里的内容
详情页json
这里获取用BeautifulSoup 获取 title 很方便,直接去第一个 title 就好了,关键就在这个image的提取。
image.png
这里是在红色框里的,这里涉及到了正则的用法,代码里用到了反斜杠,这里是转义匹配,要不然正则会匹配不到想要的数据。
还有在源代码中出现了好多反斜杠,不去除掉还是没办法匹配。
这些坑跨过之后就一帆风顺了。
配置文件
1 '''配置文件''' 2 3 4 #链接地址 5 MONGO_URL='localhost' 6 7 #数据库名称 8 MONGO_DB='toutiao' 9 10 #表名称 11 MONGO_TABLE='toutiao' 12 13 GROUP_START=1 14 GROUP_END=20 15 16 KEYWORD = '街拍'
另一种爬取今日头条美图
1 import requests 2 from urllib.parse import urlencode 3 from requests import codes 4 import os 5 from hashlib import md5 6 from multiprocessing.pool import Pool 7 8 9 def get_page(offset): 10 params = { 11 'offset': offset, 12 'format': 'json', 13 'keyword': '街拍', 14 'autoload': 'true', 15 'count': '20', 16 'cur_tab': '1', 17 'from': 'search_tab' 18 } 19 base_url = 'https://www.toutiao.com/search_content/?' 20 url = base_url + urlencode(params) 21 try: 22 resp = requests.get(url) 23 if codes.ok == resp.status_code: 24 return resp.json() 25 except requests.ConnectionError: 26 return None 27 28 29 def get_images(json): 30 if json.get('data'): 31 data = json.get('data') 32 for item in data: 33 if item.get('cell_type') is not None: 34 continue 35 title = item.get('title') 36 images = item.get('image_list') 37 for image in images: 38 yield { 39 'image': 'https:' + image.get('url'), 40 'title': title 41 } 42 43 44 def save_image(item): 45 img_path = 'img' + os.path.sep + item.get('title') 46 if not os.path.exists(img_path): 47 os.makedirs(img_path) 48 try: 49 resp = requests.get(item.get('image')) 50 if codes.ok == resp.status_code: 51 file_path = img_path + os.path.sep + '{file_name}.{file_suffix}'.format( 52 file_name=md5(resp.content).hexdigest(), 53 file_suffix='jpg') 54 if not os.path.exists(file_path): 55 with open(file_path, 'wb') as f: 56 f.write(resp.content) 57 print('Downloaded image path is %s' % file_path) 58 else: 59 print('Already Downloaded', file_path) 60 except requests.ConnectionError: 61 print('Failed to Save Image,item %s' % item) 62 63 64 def main(offset): 65 json = get_page(offset) 66 for item in get_images(json): 67 print(item) 68 save_image(item) 69 70 71 GROUP_START = 0 72 GROUP_END = 7 73 74 if __name__ == '__main__': 75 pool = Pool() 76 groups = ([x * 20 for x in range(GROUP_START, GROUP_END + 1)]) 77 pool.map(main, groups) 78 pool.close() 79 pool.join()