人生苦短,我用python--分分钟下载知乎美图给你看

上次说了要爬知乎的图片,于是花了一下午的时间去完成这件事,发现暂时接触到的爬虫总是逃脱不了一个规律:

  • 模拟登陆
  • 获取真实网页HTML源代码
  • 解析获取到的网页源代码
  • 获取想要的资源(下载到某个文件夹或者输出到表格中整合起来)

也许和我说的有一些出入,应该是刚学这个东西的原因,接下来还想研究一下多线程爬虫、添加代理、爬取海量数据并整合成图标形式,先把能做的做了。

因为是在上一次的基础上进行的,所以没有看上一篇文章的可以先看一下,这里用到的工具跟之前一样:

  • win7 64位 旗舰版
  • Python 3.5 64-bit
  • PyCharm

这里模拟登陆是跟之前一样的代码,直接贴就是:

logn_url = 'http://www.zhihu.com/#signin'

session = requests.session()

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
}

content = session.get(logn_url, headers=headers).content
soup = BeautifulSoup(content, 'html.parser')


def getxsrf():
    return soup.find('input', attrs={'name': "_xsrf"})['value']
    # 获取验证码
def get_captcha():
    t = str(int(time.time() * 1000))
    captcha_url = 'http://www.zhihu.com/captcha.gif?r=' + t + "&type=login"
    r = session.get(captcha_url, headers=headers)
    with open('captcha.jpg', 'wb') as f:
        f.write(r.content)
        f.close()
    # 用pillow 的 Image 显示验证码
    # 如果没有安装 pillow 到源代码所在的目录去找到验证码然后手动输入
    try:
        im = Image.open('captcha.jpg')
        im.show()
        im.close()
    except:
        print(u'请到 %s 目录找到captcha.jpg 手动输入' % os.path.abspath('captcha.jpg'))
    captcha = input("please input the captcha\n>")
    return captcha


def isLogin():
    # 通过查看用户个人信息来判断是否已经登录
    url = "https://www.zhihu.com/settings/profile"
    login_code = session.get(url, allow_redirects=False).status_code
    if int(x=login_code) == 200:
        return True
    else:
        return False


def login(secret, account):
    # 通过输入的用户名判断是否是手机号
    if re.match(r"^1\d{10}$", account):
        print("手机号登录 \n")
        post_url = 'http://www.zhihu.com/login/phone_num'
        postdata = {
            '_xsrf': getxsrf(),
            'password': secret,
            'remember_me': 'true',
            'phone_num': account,
        }
    else:
        print("邮箱登录 \n")
        post_url = 'http://www.zhihu.com/login/email'
        postdata = {
            '_xsrf': getxsrf(),
            'password': secret,
            'remember_me': 'true',
            'email': account,
        }
    try:
        # 不需要验证码直接登录成功
        login_page = session.post(post_url, data=postdata, headers=headers)
        login_code = login_page.text
        print(login_page.status)
        print(login_code)
    except:
        # 需要输入验证码后才能登录成功
        postdata["captcha"] = get_captcha()
        login_page = session.post(post_url, data=postdata, headers=headers)
        login_code = eval(login_page.text)
        print(login_code['msg'])

这里的代码来自GitHub上的fuck-login项目,在此表示感谢,我在原始代码上进行了改进,原始代码是适配了Python2.x和Python3.x,但是我学的是Python3.x所以去掉了一些我没用过的模块,也就是说我改进了后的代码是适用于Python3.x的。

下面就是准备获取图片了,先找一个目标,最近有一个问题很火:

长得好看,但没有男朋友是怎样的体验

还记得我列出来的步骤么,模拟登陆之后是获取真实的网页源代码,什么叫真实的,这个问题问得好,你没发现知乎很喜欢用动态加载技术么,也就是说,你看到的只是表象,这里也一样。

问题界面

来,我们先点赞数最高的妹子上传的图片:

Beauty

咳咳咳,好像跑偏了,我们的目标是星辰大海,正确的做法是鼠标右键查看网页源代码:

网页源代码

是不是看到了很多图片链接,当然我们要找.jpg、.jpeg、.png后缀的:

[站外图片上传中……(8)]

这里有两个:data-originaldata-actualsrc,实际查看的图片是data-original的图片比data-actualsrc的大,下载下来也是如此,但是因为是使用正则去匹配规则,而data-original有多项,上面代码只是贴出来的一部分,实际匹配的结果类似这样:

data-actualsrc

data-actualsrc="https://pic2.zhimg.com/be7600989233bdf438e5ba23f2cdb685_b.jpg">
data-actualsrc="https://pic2.zhimg.com/b6274542f3785c27ab4a38d4db906efd_b.jpg">

data-original

data-original="https://pic2.zhimg.com/be7600989233bdf438e5ba23f2cdb685_r.jpg">
data-original="https://pic2.zhimg.com/be7600989233bdf438e5ba23f2cdb685_r.jpg" data-actualsrc="https://pic2.zhimg.com/be7600989233bdf438e5ba23f2cdb685_b.jpg">
data-original="https://pic2.zhimg.com/b6274542f3785c27ab4a38d4db906efd_r.jpg">
data-original="https://pic2.zhimg.com/b6274542f3785c27ab4a38d4db906efd_r.jpg" data-actualsrc="https://pic2.zhimg.com/b6274542f3785c27ab4a38d4db906efd_b.jpg">
data-original="https://pic2.zhimg.com/0930549116d22ffce22e98c32683d621_r.jpg">

这是在同一段网页源码测试下的结果,匹配后一种会得到多个相同的url地址,解析起来也更麻烦,这也跟正则写的简单有关系,有兴趣的可以到时候自己修改一下正则表达式,这样下下来的图片也更高清的多。

分析了正则,下面要获取所有的图片该分析Chrome开发者面板的Post数据,因为知乎默认只显示部分回答,我们可以不断往下拉,直到看到这个:

更多

点击的时候注意观察开发者面板:

enter image description here

简直完美,传递的数据:

method:next
params:{"url_token":37709992,"pagesize":10,"offset":30}

很眼熟,url_token就是问题后面那串数字:

https://www.zhihu.com/question/37709992

pagesize是固定的10,最后一个offset偏移量同样很好理解,这里显示10应该说的就是默认显示的10个答案,后面还查看到如下数据:

method:next
params:{"url_token":37709992,"pagesize":10,"offset":20}
method:next
params:{"url_token":37709992,"pagesize":10,"offset":30}

也就是说我们在浏览器上每翻过10个答案浏览器就会向服务器发送Post请求在加载十个答案,恩差不多可以开始写代码了。

模拟登陆之后的操作是找到Post的真实地址模拟浏览器向服务器发送请求:

    url = 'https://www.zhihu.com/node/QuestionAnswerListV2'
    header = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
        'Referer': 'https://www.zhihu.com/question/37709992',
        'Origin': 'https://www.zhihu.com',
        'Accept-Encoding': 'gzip, deflate, br',
    }
    data = {
        'method': 'next',
        'params': '{"url_token":' + str(37709992) + ',"pagesize": "10",' + \
                  '"offset":' + str(offset) + "}",
        '_xsrf': getxsrf(),

    }

注意

发送Post请求时候请加上'_xsrf': getxsrf()这一行,否则的话返回的只会是404 Forbidden,应该是做了防伪登陆的缘故

然后是写正则,这里发现图片都是被包含在这里面:

<div class="zm-editable-content clearfix">
...
...
</div>

所以先匹配到这一大串内容:

        pattern = re.compile('<a class="author-link".*?<span title=.*?<div class="zh-summary.*?' +
                             '<div class="zm-editable-content.*?>(.*?)</div>', re.S)

然后在匹配data-actualsrc里面的图片链接:

pattern = re.compile('data-actualsrc="(.*?)">', re.S)

还有一点要注意的是我们请求之后返回来的是json格式的数据,所以这里还要用到json模块:

 question = session.post(url, headers=header, data=data)
 dic = json.loads(question.content.decode('ISO-8859-1'))
 li = dic['msg'][0]

然后对其进行解析:

# 这里使用的是第一个正则表达式
items = re.findall(pattern, li)
# 接下来
items = re.findall(pattern, li)
# 存储图片链接
imagesurl = []
pattern = re.compile('data-actualsrc="(.*?)">', re.S)
for item in items:
    urls = re.findall(pattern, item)
    imagesurl.extend(urls)

执行下载操作:

# 存放图片的地址
PWD = "D:/work/python/zhihu/"
        for url in imagesurl:
            myurl = url
            filename = PWD + str(count) + '.jpg'
            if os.path.isfile(filename):
                print("文件存在:", filename)
                count += 1
                continue
            else:
            # 执行下载操作的方法
                downpic(filename, myurl)
                count += 1
                photoNum += 1
            print("一共下载了{0} 张照片".format(photoNum))
            if not os.path.exists(PWD):
                os.makedirs(PWD)
                # 递归调用
        change(offset, count, photoNum)

# downpic方法源码
def downpic(filename, url):
    print("正在下载 " + url)
    try:
        r = requests.get(url, stream=True)
        with open(filename, 'wb') as fd:
            for chunk in r.iter_content():
                fd.write(chunk)
    except Exception as e:
        print("下载失败了", e)

运行结果

运行结果
结果

这只是一部分,我之前下了四五百张还在下~当然这是后话,感觉现在写的东西都很简单,希望下一次能写出难一点的东西出来。

这里正则部分参考了这里:

通过Python爬虫爬取知乎某个问题下的图片

最后是源码

源码中注释部分只能下载前十个答案里包含的图片的方法,还有一些想法未完成,本来是想打印一下正在下载哪个答主的回答,然后把图片分别保存到相应的单独文件夹,实现起来有点麻烦就没去搞,仅供参考。
亲测如果需要下载另一个问题的答案,只需要在:

 data = {
        'method': 'next',
        'params': '{"url_token":' + str(37709992) + ',"pagesize": "10",' + \
                  '"offset":' + str(offset) + "}",
        '_xsrf': getxsrf(),

    }

更换那串数字就行,就好比这样的形式:

https://www.zhihu.com/question/48720845

但是这种形式的把数字换上去不起效:

https://www.zhihu.com/question/49078894#answer-41776282

这个好像是知乎热门问答的链接形式,暂时没有深究
最后,觉得写得不错的可以关注一下我的公众号呀~蟹蟹

735909-e84879b3318d959d.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,498评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,358评论 0 17
  • 秋播 图文/石丰 【老家记事】昨日和今日关中老家的天气,非常晴朗,秋高气爽,清风徐来,一扫多日连阴秋雨和湿气。 从...
    石丰当代艺术阅读 563评论 0 0
  • 喜欢吃蛋糕的人很多,享受午后闲暇时光,冰饮爽后,蛋糕甜而不腻,让心情无比美丽。我想,这是大多数女孩子无法拒绝的...
    莫倪卡阅读 318评论 0 0