【Python爬虫实战】——爬取今日头条美女图片

笔者是头条的深度使用者,经常用头条完成“看片”大业。若不信的话可以试试在头条搜索街拍,返回的都是一道道靓丽的风景线。

想把图片存下来,该怎么办呢?我们可以用Python爬虫啊。

人生苦短,我用Python!

1、工具

Python3.5,Sublime Text,Windows 7

2、分析(第三步有完整代码)

可以看到搜索结果默认返回了 20 篇文章,当页面滚动到底部时头条通过 ajax 加载更多文章,浏览器按下 F12 打开调试工具(我的是 Chrome),点击 Network 选项,尝试加载更多的文章,可以看到相关的 http 请求:

此次返回Request URL: http://www.toutiao.com/search_content/?offset=20&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&cur_tab=1 来试试返回了什么

import json
from urllib import request

url = "http://www.toutiao.com/search_content/?offset=20&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&cur_tab=1"

with request.urlopen(url) as res:
    d = json.loads(res.read().decode())
    print(d)

发现我们需要的东西在'data'里,打开一篇文章,来试试如何下载单篇图片。

import json
from urllib import request

url = 'http://www.toutiao.com/a6314996711535444226/#p=1'

with request.urlopen(url) as res:
    soup = BeautifulSoup(res.read().decode(errors='ignore'), 'html.parser')
    article_main = soup.find('div', id='article-main')
    photo_list = [photo.get('src') for photo in article_main.find_all('img') if photo.get('src')]
    print(photo_list)

输出 ['http://p3.pstatp.com/large/159f00010b30d6736512', 'http://p1.pstatp.com/large/1534000488c40143b9ce', 'http://p3.pstatp.com/large/159d0001834ff61ccb8c', 'http://p1.pstatp.com/large/1534000488c1cd02b5ed'] 首先用BeautifulSoup解析网页,通过 find 方法找到 article-main 对应的 div 块,在该 div 块下继续使用 find_all 方法搜寻全部的 img 标签,并提取其 src 属性对应的值,于是我们便获得了该文章下全部图片的 URL 列表。 接下来就是保存图片。

photo_url = "http://p3.pstatp.com/large/159f00010b30d6736512"
photo_name = photo_url.rsplit('/', 1)[-1] + '.jpg'

with request.urlopen(photo_url) as res, open(photo_name, 'wb') as f:
    f.write(res.read())

基本步骤就是这么多了,整理下爬取流程:

  1. 指定查询参数,向 http://www.toutiao.com/search_content/ 提交我们的查询请求。
  2. 从返回的数据(JSON 格式)中解析出全部文章的 URL,分别向这些文章发送请求。
  3. 从返回的数据(HTML 格式)提取出文章的标题和全部图片链接。
  4. 再分别向这些图片链接发送请求,将返回的图片输入保存到本地(E:\jiepai)。
  5. 修改查询参数,以使服务器返回新的文章数据,继续第一步。

3、完整代码

import re
import json
import time
import random

from pathlib import Path
from urllib import parse
from urllib import error
from urllib import request
from datetime import datetime
from http.client import IncompleteRead
from socket import timeout as socket_timeout

from bs4 import BeautifulSoup


def _get_timestamp():
    """
    向 http://www.toutiao.com/search_content/ 发送的请求的参数包含一个时间戳,
    该函数获取当前时间戳,并格式化成头条接收的格式。格式为 datetime.today() 返回
    的值去掉小数点后取第一位到倒数第三位的数字。
    """
    row_timestamp = str(datetime.timestamp(datetime.today()))
    return row_timestamp.replace('.', '')[:-3]


def _create_dir(name):
    """
    根据传入的目录名创建一个目录,这里用到了 python3.4 引入的 pathlib 库。
    """
    directory = Path(name)
    if not directory.exists():
        directory.mkdir()
    return directory


def _get_query_string(data):
    """
    将查询参数编码为 url,例如:
    data = {
            'offset': offset,
            'format': 'json',
            'keyword': '街拍',
            'autoload': 'true',
            'count': 20,
            '_': 1480675595492
    }
    则返回的值为:
    ?offset=20&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&_=1480675595492"
    """
    return parse.urlencode(data)


def get_article_urls(req, timeout=10):
    with request.urlopen(req, timeout=timeout) as res:
        d = json.loads(res.read().decode()).get('data')

        if d is None:
            print("数据全部请求完毕...")
            return

        urls = [article.get('article_url') for article in d if article.get('article_url')]
        return urls


def get_photo_urls(req, timeout=10):
    with request.urlopen(req, timeout=timeout) as res:
        # 这里 decode 默认为 utf-8 编码,但返回的内容中含有部分非 utf-8 的内容,会导致解码失败
        # 所以我们使用 ignore 忽略这部分内容
        soup = BeautifulSoup(res.read().decode(errors='ignore'), 'html.parser')
        article_main = soup.find('div', id='article-main')

        if not article_main:
            print("无法定位到文章主体...")
            return

        heading = article_main.h1.string

        if '街拍' not in heading:
            print("这不是街拍的文章!!!")
            return

        img_list = [img.get('src') for img in article_main.find_all('img') if img.get('src')]
        return heading, img_list


def save_photo(photo_url, save_dir, timeout=10):
    photo_name = photo_url.rsplit('/', 1)[-1] + '.jpg'

    # 这是 pathlib 的特殊操作,其作用是将 save_dir 和 photo_name 拼成一个完整的路径。例如:
    # save_dir = 'E:\jiepai'
    # photo_name = '11125841455748.jpg'
    # 则 save_path = 'E:\jiepai\11125841455748.jpg'
    save_path = save_dir / photo_name

    with request.urlopen(photo_url, timeout=timeout) as res, save_path.open('wb') as f:
        f.write(res.read())
        print('已下载图片:{dir_name}/{photo_name},请求的 URL 为:{url}'
              .format(dir_name=dir_name, photo_name=photo_name, url=a_url))


if __name__ == '__main__':
    ongoing = True
    offset = 0  # 请求的偏移量,每次累加 20
    root_dir = _create_dir('E:\jiepai')  # 保存图片的根目录
    request_headers = {
        'Referer': 'http://www.toutiao.com/search/?keyword=%E8%A1%97%E6%8B%8D',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36'
    }

    while ongoing:
        timestamp = _get_timestamp()
        query_data = {
            'offset': offset,
            'format': 'json',
            'keyword': '街拍',
            'autoload': 'true',
            'count': 20,  # 每次返回 20 篇文章
            '_': timestamp
        }
        query_url = 'http://www.toutiao.com/search_content/' + '?' + _get_query_string(query_data)
        article_req = request.Request(query_url, headers=request_headers)
        article_urls = get_article_urls(article_req)

        # 如果不再返回数据,说明全部数据已经请求完毕,跳出循环
        if article_urls is None:
            break

        # 开始向每篇文章发送请求
        for a_url in article_urls:
            # 请求文章时可能返回两个异常,一个是连接超时 socket_timeout,
            # 另一个是 HTTPError,例如页面不存在
            # 连接超时我们便休息一下,HTTPError 便直接跳过。
            try:
                photo_req = request.Request(a_url, headers=request_headers)
                photo_urls = get_photo_urls(photo_req)

                # 文章中没有图片?跳到下一篇文章
                if photo_urls is None:
                    continue

                article_heading, photo_urls = photo_urls

                # 这里使用文章的标题作为保存这篇文章全部图片的目录。
                # 过滤掉了标题中在 windows 下无法作为目录名的特殊字符。
                dir_name = re.sub(r'[\\/:*?"<>|]', '', article_heading)
                download_dir = _create_dir(root_dir / dir_name)

                # 开始下载文章中的图片
                for p_url in photo_urls:
                    # 由于图片数据以分段形式返回,在接收数据时可能抛出 IncompleteRead 异常
                    try:
                        save_photo(p_url, save_dir=download_dir)
                    except IncompleteRead as e:
                        print(e)
                        continue
            except socket_timeout:
                print("连接超时了,休息一下...")
                time.sleep(random.randint(15, 25))
                continue
            except error.HTTPError:
                continue

        # 一次请求处理完毕,将偏移量加 20,继续获取新的 20 篇文章。
        offset += 20

同理,只需修改代码,就可以下载想要的关键词,自己动手,想啥有啥。

原文发布于微信公众号 - Python爬虫与算法进阶(zhangslob)

原文发表时间:2017-01-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端儿

【转】http-equiv="X-UA-Compatible" 设置IE浏览器兼容模式详解

文件兼容性用于定义让IE如何编译你的网页。此文件解释文件兼容性,如何指定你网站的文件兼容性模式以及如何判断一个网页该使用的文件模式。

35710
来自专栏文渊之博

部署和使用kibana

背景 本文将主要介绍ELK的可视化工具Kibana的部署和使用。主要分为三个步骤来实现最终呈现:   1.导入数据到ES;   2.部署kiban...

276100
来自专栏Python中文社区

Python爬虫抓取收集考试大纲

專 欄 ❈ Garfield_Liang,Python中文社区专栏作者。 博客地址:http://www.jianshu.com/u/cac1d39abfa9 ...

260100
来自专栏java架构师

跨域请求数据解决方案整理

跨域请求数据解决方案主要有如下解决方法: JSONP方式 表单POST方式 服务器代理 Html5的XDomainRequest Flash request 分...

36270
来自专栏FreeBuf

玩转Google的XSS游戏

作者 Taskiller Hi基友们,本文主要描述Google前些天发布的关于XSS漏洞游戏的玩法,地址在这里。 https://xss-game.appsp...

269100
来自专栏狮乐园

高级 Angular 组件模式 (3a)

针对第一个问题,我们使用@ContentChildren装饰器(因为它获取所有的子组件引用),但是它无法解决第二个问题。

11040
来自专栏FreeBuf

软件漏洞分析技巧分享

作者:riusksk【TSRC】 在日常分析软件漏洞时,经常需要耗费比较长的分析时间,少则几小时,多则数天,甚至更久。因此,经常总结一些分析技巧是非常有必要的,...

29990
来自专栏安恒网络空间安全讲武堂

Amazing phpinfo()

前记 Xdebug 前记 定义 开启Xdebug 适用目标 实验效果 注意事项 session.upload_progress 定义 开启session.upl...

37960
来自专栏猿人谷

使用bash编写Linux shell脚本--调试和版本控制

当我还在布鲁克大学上学的时候, Macquarium 实验室中充满了苹果公司的 Macintosh Plus 电脑。一天,我在为第三年的操作系统课程准备一个程序...

516110
来自专栏芋道源码1024

Java中高级面试题(4)

这里选了几道高频面试题以及一些解答。不一定全部正确,有一些是没有固定答案的,如果发现有错误的欢迎纠正,如果有更好的回答,热烈欢迎留言探讨。

20800

扫码关注云+社区

领取腾讯云代金券