欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

今日头条爬虫实战----爬取图片

程序员文章站 2022-04-26 09:39:03
...

今日头条爬虫实战----爬取图片

点击上方“蓝字”关注我们

今日头条爬虫实战----爬取图片

今日头条爬虫实战

Mar 27, 2020

今日头条爬虫实战----爬取图片

本期介绍通过在头条中搜索关键词后,分析ajax内容来爬取相关图片

本文约1.8k字,预计阅读10分钟。

今日头条爬虫实战----爬取图片

有时候我们在用 「requests」抓取页面的时候,得到的结果可能和在浏览器中看到的不一样:在浏览器中可以看到正常显示的页面数据,但是使用 requests得到的结果并没有。这是因为 requests获取的都是原始的HTML 文档,而浏览器中的页面则是经过 JavaScript处理数据后生成的结果,这些数据的来源有多种,可能是通过 Ajax加载的, 可能是包含在 HTML 文档中的,也可能是经过 JavaScript和特定算法计算后生成的

对于第一种情况,数据加载是一种异步加载方式,原始的页面最初不会包含某些数据,原始页面加载完后,会再向服务器请求某个接口获取数据,然后数据才被处理从而呈现到网页上,这其实就是发送了一个「Ajax」请求 。

所以如果遇到这样的页面,直接利用 requests等库来抓取原始页面,是无法获取到有效数据的,这时需要分析网页后台向接口发送的「 Ajax」请求,如果可以用「 requests」来模拟 「Ajax」请求,那么就可以成功抓取了 。

页面分析

打开Chrome/Safrai中的「检查」---「网络」,在头条官网上进行搜索,例如:天气之子,向下滚动,加载所有的信息,部分结果如下所示:

今日头条爬虫实战----爬取图片

选择「XHR」,进行预览,分析「data」,我们主要是获取图片和它的标题(方便分类保存),如下所示:

今日头条爬虫实战----爬取图片

今日头条爬虫实战----爬取图片

我们观察发现:

  • 图片的url都属于image_list字段,且data中表明了大图片的url格式,我们可以对提取的url更改为大图片的url;

  • data中有些并不包含image_list或title,可以将这些情况去除;

加载单个Ajax请求

我们首先对请求头部进行分析:

今日头条爬虫实战----爬取图片

今日头条爬虫实战----爬取图片

通过对两个URL的格式分析,变化的参数只有「offset」,它的变化规律为0,20,40,...我们可以推断出它是一个偏移量,故我们可以设置参数offset,方便后续去加载所有的Ajax请求。

获取单个页面的json数据代码如下:

def get_page(offset):
  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',
    'timestamp' : int(time.time_ns() / (10**6))
  }
  headers = {
  # cookie加入重中之重
  'cookie': ('__tasessionId=q6mp1lrxu1583224640956;' 
    'csrftoken=4d4dc07f33b82883b72743f5c81537ab;' 
    's_v_web_id=verify_k7bn2ml4_15YGHeUM_iH2m_4qAg_A8Ec_UlarLgh3Pcej;' 
    'SLARDAR_WEB_ID=4177e739-d79a-44bc-b568-502f52fe45d2;' 
    'tt_scid=7nzno7juT5MPzFuVZIr2zTCJKKlx-nEheWIm47qBeDd7PRKAcGCk-XD5jpZADunmcb54;' 
    'tt_webid=6799872105451128333;' 
    'tt_webid=6799872105451128333;' 
    'ttcid=2204991f51f148848ab9d33a3b1c85a639;' 
    'WEATHER_CITY=%E5%8C%97%E4%BA%AC'),
  'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537/36'+
  '(KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537/36'
  }
  url = 'https://www.toutiao.com/api/search/content/?' + urlencode(params)
  try:
    res = requests.get(url, headers=headers)
    if res.status_code == 200:
      return res.json()
  except requests.ConnectionError:
    return None

注:

  • 「请求头部需要添加cookie,不然返回为空。」

  • urlencode:将参数进行序列化; 

  • 返回爬取页面的json形式,再对其进行解析时就不需要解析库进行分析了;

获取图片

返回的json为一个字典,我们需要它key为data的value,然后对data进行遍历:

  • 首先判断当前是否存在title和image_list字段,不存在则跳过;

  • 获取title和image_list对应的值,接着需要遍历image_list:

    • 通过上述对图片的分析,我们需要大图片,故通过re.sub()方法对部分字符进行替换,因为存在两种格式,故分别进行分析替换

  • 将图片和标题进行返回,此时来构造一个生成器(发现比直接使用list好用多了)

代码如下:

def get_images(json):
  if json.get('data'):
    for item in json.get('data'):
      if item.get('title') and item.get('image_list'):
         title = item.get('title')
         images = item.get('image_list')
         for image in images:
           if "190x124" in image.get('url'):
             image_url = re.sub('list/190x124', 'large', image.get('url'))
           else:
             image_url = re.sub('list', 'large', image.get('url'))
           yield{
           'image': image_url,
           'title': title
           }

保存图片

我们将所有的内容放在data/tianqizhizi下,并将每一个标题作为一个文件夹,生成对应目录;

代码如下:

def save_images(item):
  path = 'data/tinaiqzhizi/' + item.get('title')
  if not os.path.exists(path):
    os.makedirs(path)
  try:
    res = requests.get(item.get('image'))
    if res.status_code == 200:
      print(item)
      file_path = '{0}/{1}.{2}'.format(path, 
        md5(res.content).hexdigest(), 'png')
      if not os.path.exists(file_path):
        with open(file_path, 'wb') as f:
          f.write(res.content)
      else:
        print('Already Download', file_path)
  except requests.ConnectionError:
    print('Falied to Save Image')

注:

  • 通过图片的url获取对应的二进制信息。

  • 图片的名称可以使用其内容的 MD5 值,这样可以去除重复。

  • 将二进制内容写入。

主函数

加载单个ajax后保存图片:

def main(offset):
  json = get_page(offset)
  for item in get_images(json):
    save_images(item)

实现多进程进行下载:

if __name__ == '__main__':
  pool = Pool()
  groups = [x * 20 for x in range(0, 9)]
  pool.map(main, groups)
  pool.close()
  pool.join()

GitHub

GitHub地址:https://github.com/BlackSpaceGZY/Crawler

参考文献

[1] 崔庆才.python3网络爬虫实战[M].北京:人民邮电出版社,2018.

今日头条爬虫实战----爬取图片

扫码关注更多精彩

今日头条爬虫实战----爬取图片

今日头条爬虫实战----爬取图片

今日头条爬虫实战----爬取图片