import requests from urllib.parse import urlencode from requests import codes import os from hashlib import md5 from multiprocessing.pool import Pool import re from selenium import webdriver def get_cookies(url): str='' options = webdriver.ChromeOptions() options.add_argument('--headless') options.binary_location=r"C:Program FilesGoogleChromeApplicationchrome.exe" browser = webdriver.Chrome(options=options) browser.get(url) for i in browser.get_cookies(): try: name=i.get('name') value=i.get('value') str=str+name+'='+value+';' except ValueError as e: print(e) return str cookies = get_cookies('https://www.toutiao.com') def get_page(offset): headers = { 'cookie': cookies, 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/85.0.564.44', 'x-requested-with': 'XMLHttpRequest', 'referer': 'https://www.toutiao.com/search/?keyword=%E8%A1%97%E6%8B%8D', } params = { 'aid': '24', 'app_name': 'web_search', 'offset': offset, 'format': 'json', 'keyword': '街拍', 'autoload': 'true', 'count': '20', 'en_qc': '1', 'cur_tab': '1', 'from': 'search_tab', 'pd': 'synthesis', } base_url = 'https://www.toutiao.com/api/search/content/?' url = base_url + urlencode(params) # print(url) try: resp = requests.get(url, headers=headers) if 200 == resp.status_code: return resp.json() except requests.ConnectionError: return None def get_images(json): if json.get('data'): data = json.get('data') for item in data: if item.get('title') is None: # 刨掉前部分无关内容 continue title = re.sub('[ ]', '', item.get('title')) # 获取标题 url = item.get("article_url") #获取子链接 if url == None: continue try: headers = { 'cookie': cookies, 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/85.0.564.44', 'x-requested-with': 'XMLHttpRequest', 'referer': 'https://www.toutiao.com/search/?keyword=%E8%A1%97%E6%8B%8D', } resp = requests.get(url,headers=headers) if 200 == resp.status_code: images_pattern = re.compile('JSON.parse("(.*?)"), ',re.S) result = re.search(images_pattern,resp.text) if result == None: url_list = re.findall(r'https:(.*?)?from=pc', resp.text) for url in url_list: yield { 'image': 'https:'+ url.encode("utf-8").decode("unicode-escape").replace('=', '=') + 'from=pc', 'title': title } else: url_list = re.findall(r'https:(.*?)_tt?from=pc', resp.text) for url in url_list: yield { 'image': 'https:'+ url.encode("utf-8").decode("unicode-escape")+ '_tt?from=pc', 'title': title } except requests.ConnectionError: # 打开子链接失败就直接保存图集中前部分 images = item.get('image_list') for image in images: origin_image = re.sub("list.*?pgc-image", "large/pgc-image", image.get('url')) # 改成origin/pgc-image是原图 yield { 'image': origin_image, 'title': title } def replace_all_blank(value): """ 去除value中的所有非字母内容,包括标点符号、空格、换行、下划线等 :param value: 需要处理的内容 :return: 返回处理后的内容 """ # W 表示匹配非数字字母下划线 result = re.sub('W+', '', value).replace("_", '') return result def save_image(item): img_path = 'img' + os.path.sep + replace_all_blank(item.get('title')) if not os.path.exists(img_path): os.makedirs(img_path) # 生成目录文件夹 try: resp = requests.get(item.get('image')) if codes.ok == resp.status_code: file_path = img_path + os.path.sep + '{file_name}.{file_suffix}'.format( file_name=md5(resp.content).hexdigest(), file_suffix='jpg') # 单一文件的路径 if not os.path.exists(file_path): with open(file_path, 'wb') as f: f.write(resp.content) print('Downloaded image path is %s' % file_path) else: print('Already Downloaded', file_path) except Exception as e: print(e) def main(offset): json = get_page(offset) for item in get_images(json): save_image(item) if __name__ == '__main__': ''' for i in range(3): main(20*i) ''' pool = Pool() groups = ([x * 20 for x in range(0, 3)]) pool.map(main, groups)
发现头条会有爬取限制,原因是timestamp和signature会限制时间内的资源访问,而且现在没有加代理池,高强度爬取会error。
和崔庆才的爬取策略不同,现在都是动静分离,cdn负载,前后端分离,所以不能简单地去根据list页返回的ajax内的json里image_list来获得url了,这样爬取的图只有缩略的前几张,也不是原图
所以我们这边直接由列表页内的json解析出来的artcle_url,直接访问api获得详情页的包含js标签渲染的html代码,通过re正则获得想要的图片url,然后对包含unicode码的url字符串进行转义,这里用到encode和decode(unicode-escape)编码集反向编码
这样我们就能获得浏览器点击进详情页右键将图片保存到本地的效果。
这边js没有加密,只是简单的unicode编码,关于编码、json转换、字符串转换这方面还要学习。
在python中,unicode(统一码 采用双字节对字符进行编码)是内存编码集,一般我们将数据存储到文件时,需要将数据先编码(encode)为其他编码集,比如utf-8、gbk等。
读取数据的时候再通过同样的编码集进行解码(decode)即可。 xxx xxx xxx xxx 一般是双字节码,也就是python内b开头的字节码格式
unicode-escape编码集,它是将unicode内存编码值直接存储。uxxxx uxxxx uxxxx uxxxx 是将 unicode内存编码值,没有用字节码进行编码
还有就是,没有搭建专用的代理池对爬取效率有很大的影响,这边主要是一个练手
我们需要识别要爬取的资源比如列表页肯定是ajax异步加载的,然后构建query字符串设置分页就行了,
还是像详情页里面是直接加载完的,由此我们从html里就能获得链接,虽然有时候不是明文,有的增加了编码或者进行了字符串加密。
还有就是头条是对webdriver和爬虫有识别机制的,单纯的构造ua和代理只能起一部分作用
所以如何构造cookies,里面关键的token值,signature算法和timestamp,对于更有效率的大规模分布式的爬取才是关键。
当然头条系和其他产品现在都从web端进入了app端,所以单纯的web页面爬取其实现在并没有什么价值
如何搞定app端的数据尤为重要,这之后也对我们的js能力,app逆向能力提出了比较大的要求。说到底这就是个玩具罢了,毕竟纸船也是船,吃不上猪肉也要看看猪是怎么上树的。
XD