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

来源:互联网 发布:网络教育和函授哪个好 编辑:程序博客网 时间:2024/06/11 03:59

该爬虫的完整代码我把它放到了GitHub上,因为目前是在一点点的增加功能阶段,所以代码可能没有完善好,但是正常运行时没有问题的,欢迎拍砖,:)

GitHub:点击打开链接

该爬虫主要是通过requests来实现的,该模块完全可以很好的代替urllib和urllib2,而且功能更强大,详细可以看这里。同时也用到了pillow模块中的image对象,实现环境是Python2,不过在Python3上只需很小的改动就可以正常运行,等后续代码功能完善好后,我会把Python3的实现也整理一份出来放在GitHub上。


首先通过cookie模拟登陆到知乎,然后获取知乎某一个问题的链接,打开并获取该问题回答下的图片,然后保存到本地。我们先看下知乎中的网页html文本,

对于某一个用户的回答是这样的:红色方框中的标签是回答的具体内容标签

然后原始图片是在下面这个标签里,包含在上图红色方框的标签下:


我们在写正则表达式的时候只需匹配到这个标签,然后取出里面的url就可以了。具体的正则表达式如下,分为两部分,首先取出”zm-editable-content.."标签里的全部内容,然后在从中取出"data-actualsrc"的内容:

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


然后我们通过模拟登陆到知乎,具体的模拟登陆解释可以看这里。

# -*-coding:utf-8 -*-import requestsfrom requests.adapters import HTTPAdapter# import urllib# import urllib2import cookielibimport reimport timeimport os.pathfrom PIL import Imageuser_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5)'headers = {'User-Agent': user_agent}session = requests.session()session.cookies = cookielib.LWPCookieJar(filename='cookies')try:    session.cookies.load(ignore_discard=True)except:    print "Cookie 未能加载"def get_xsrf():    '''_xsrf 是一个动态变化的参数'''    index_url = "http://www.zhihu.com"    index_page = session.get(index_url, headers=headers)    html = index_page.text    pattern = r'name="_xsrf" value="(.*?)"'    _xsrf = re.findall(pattern, html)    return _xsrf[0]def get_captcha():    t = str(int(time.time() * 1000))    captcha_url = 'http://www.zhihu.com/captcha.gif?r' + t + "&type=login"    print captcha_url    r = session.get(captcha_url, headers = headers)    with open('captcha.jpg', 'wb') as f:        f.write(r.content)        f.close()    try:        im = Image.open('captcha.jpg')        im.show()        im.close()    except:        print u'captcha.jpg 所在目录:%s, 手动输入'% os.path.abspath('captcha.jpg')    captcha = input("input captcha\n")    return captchadef isLogin():    url = "https://www.zhihu.com/settings/profile"    login_code = session.get(url, allow_redirects=False).status_code    print "login code: ", login_code    if int(x=login_code) == 200:        return True    else:        return Falsedef login(secret, account):    if isLogin():        print "已经登录"        return    if re.match(r"^1\d{10}$", account):        print "手机号登陆\n"        post_url = 'http://www.zhihu.com/login/phone_num'        postdata = {            '_xsrf': get_xsrf(),            'password': secret,            'remember_me': 'true',            'phone_num': account,        }    else:        print '邮箱登录\n'        post_url = 'http://www.zhihu.com/login/email'        postdata = {            '_xsrf': get_xsrf(),            '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        print 'what?'    except:        print '需要验证码'        postdata['captcha'] = get_captcha()        login_page = session.post(post_url, data=postdata, headers=headers)        login_code = eval(login_page.text)   #eval 从字符串中提取字典        u = login_code['msg']    session.cookies.save()def getPageCode(pageUrl):    try:        req = session.get(pageUrl, headers=headers)        print req.request.headers        return req.text    except urllib2.URLError, e:        if hasattr(e, 'reason'):            print u"打开链接失败...", e.reason            return None


登陆进去后,我们在打开某一个知乎问题链接,爬取里面的图片然后下载到本地目录,具体看下面的代码:注意在输入验证码的时候我们用的是input(),在Python2中用input()输入的时候,如果输入字符串,那么要在输入的字符上加上引号,否则会报错,如:“abcd"

def getPageCode(pageUrl):    try:        req = session.get(pageUrl, headers=headers)        print req.request.headers        return req.text    except urllib2.URLError, e:        if hasattr(e, 'reason'):            print u"打开链接失败...", e.reason            return Nonedef getImageUrl(pageUrl):    pageCode = getPageCode(pageUrl)    if not pageCode:        print "打开网页链接失败.."        return None    pattern = re.compile('<a class="author-link".*?<span title=.*?<div class="zh-summary.*?' +                         '<div class="zm-editable-content.*?>(.*?)</div>', re.S)    items = re.findall(pattern, pageCode)    imagesUrl = []    pattern = re.compile('data-actualsrc="(.*?)">', re.S)    for item in items:        urls = re.findall(pattern, item)        imagesUrl.extend(urls)    for url in imagesUrl:        print url    return imagesUrldef saveImagesFromUrl(pageUrl, filePath):    imagesUrl = getImageUrl(pageUrl)    if not imagesUrl:        print 'imagesUrl is empty'        return    nameNumber = 0;    for image in imagesUrl:        suffixNum = image.rfind('.')        suffix = image[suffixNum:]        fileName = filePath + os.sep + str(nameNumber) + suffix        nameNumber += 1        print 'save in: ', fileName        response = requests.get(image)        contents = response.content        try:            with open(fileName, "wb") as pic:                pic.write(contents)        except IOError:            print 'Io error'login('这里是密码','这里是你的知乎账户')saveImagesFromUrl('https://www.zhihu.com/question/46435597', '/Volumes/HDD/Picture')
注:该代码目前只能爬取到知乎某个问题下第一页的回答内容。

最后就可以把图片保存到Picture这个目录下了,当然这个爬虫目前还可以做很多的改动,比如翻页功能,然后多线程下载之类的,后续改进后我在贴上来吧。
=====================更新1:下面来增加爬取知乎时的翻页
知乎网在处理翻页的时候,不像糗事百科这种直接在网址后面加数字一二三就可以实现翻页了,而是向服务器发送post请求,然后服务器响应翻页请求。我们用谷歌开发者工具抓取来看看就清楚了,下面是打开某个问题第一页时的获取情况:
这里我们可以发现,在请求页面的时候,是向服务器请求一个连接,然后post的数据有method和params,然后我们把页面下拉到最下面点击加载更多时如下:
这是第一个QuestionAnswerListV2,是在我点击加载更多的时候产生的,里面的数据内容和第一个的基本一样,只是下面的参数“offset”偏移量不同,增加了10,可以看出在一个页面上显示了10条数据,当然这得是在我们登陆的情况下,没有登陆的话就无法继续往下执行了。
所以到这里我们就已经搞清楚了,在翻页时需要post的链接和data,那处理起来就轻松多了,下面就直接贴代码了,运行后有彩蛋喔,模拟登陆部分还是没变。
注意:下面这段代码虽然可以正常的使用了,而且设置了链接超时和重连,不过当你爬取的问题的回答数比较多的时候,可能会等得久一点。
def getImageUrl():    url = "https://www.zhihu.com/node/QuestionAnswerListV2"    method = 'next'    size = 10    allImageUrl = []    #循环直至爬完整个问题的回答    while(True):        print '===========offset: ', size        postdata = {            'method': 'next',            'params': '{"url_token":' + str(46435597) + ',"pagesize": "10",' +\                      '"offset":' + str(size) + "}",            '_xsrf':get_xsrf(),        }        size += 10        page = session.post(url, headers=headers, data=postdata)        ret = eval(page.text)        listMsg = ret['msg']        if not listMsg:            print "图片URL获取完毕, 页数: ", (size-10)/10            return allImageUrl        pattern = re.compile('data-actualsrc="(.*?)">', re.S)        for pageUrl in listMsg:            items = re.findall(pattern, pageUrl)            for item in items:      #这里去掉得到的图片URL中的转义字符'\\'                imageUrl = item.replace("\\", "")                allImageUrl.append(imageUrl)def saveImagesFromUrl(filePath):    imagesUrl = getImageUrl()    print "图片数: ", len(imageUrl)    if not imagesUrl:        print 'imagesUrl is empty'        return    nameNumber = 0;    for image in imagesUrl:        suffixNum = image.rfind('.')        suffix = image[suffixNum:]        fileName = filePath + os.sep + str(nameNumber) + suffix        nameNumber += 1        try:            # 设置超时重试次数及超时时间单位秒            session.mount(image, HTTPAdapter(max_retries=3))            response = session.get(image, timeout=20)            contents = response.content            with open(fileName, "wb") as pic:                pic.write(contents)        except IOError:            print 'Io error'        except requests.exceptions.ConnectionError:            print '连接超时,URL: ', image    print '图片下载完毕'login('这是你的知乎密码','这是你的知乎账户')saveImagesFromUrl('/Volumes/HDD/Picture')

下面这段代码改进了一下,开了两个线程来跑,就是一个简单的生产者消费者模型,一个线程负责从网页中提取图片的URL,一个线程负责下载和保存到文件中。运行起来没有问题,测试了下当爬到一千多张的时候就会有错,我猜想是知乎网做了反爬虫处理,所以后面的更新中打算采用代理来绕开服务器的拦截。
from threading import Threadfrom Queue import Queuequeue = Queue(50)filePath = '/Volumes/HDD/image'isRun = Trueclass GetImageURLThread(Thread):    def run(self):        url = "https://www.zhihu.com/node/QuestionAnswerListV2"        method = 'next'        size = 10        if not os.path.exists(filePath):            os.makedirs(filePath)        # 循环直至爬完整个问题的回答        while (True):            print '===========offset: ', size            postdata = {                'method': 'next',                'params': '{"url_token":' + str(34243513) + ',"pagesize": "10",' + \                          '"offset":' + str(size) + "}",                '_xsrf': get_xsrf(),            }            size += 10            page = session.post(url, headers=headers, data=postdata)            ret = eval(page.text)            listMsg = ret['msg']            if not listMsg:                print "图片URL获取完毕, 页数: ", (size-10) / 10                queue.join()                isRun = False                break            pattern = re.compile('data-actualsrc="(.*?)">', re.S)            global queue            for pageUrl in listMsg:                items = re.findall(pattern, pageUrl)                for item in items:  # 这里去掉得到的图片URL中的转义字符'\\'                    imageUrl = item.replace("\\", "")                    queue.put(imageUrl)class DownloadImgAndWriteToFile(Thread):    def run(self):        nameNumber = 0        global queue        while isRun:            image = queue.get()            queue.task_done()            suffixNum = image.rfind('.')            suffix = image[suffixNum:]            fileName = filePath + os.sep + str(nameNumber) + suffix            nameNumber += 1            try:                # 设置超时重试次数及超时时间单位秒                session.mount(image, HTTPAdapter(max_retries=3))                response = session.get(image, timeout=20)                contents = response.content                with open(fileName, "wb") as pic:                    pic.write(contents)            except requests.exceptions.ConnectionError:                print '连接超时,URL: ', image            except IOError:                print 'Io error'        print '图片下载完毕'if __name__ == '__main__':    login('这是你的知乎密码', '这是你的知乎账户')    urlThread = GetImageURLThread()    downloadThread = DownloadImgAndWriteToFile()    urlThread.start()    downloadThread.start()    urlThread.join()    downloadThread.join()

===============更新2,设置代理,Python爬虫设置代理IP爬取知乎图片
效果类似这样:


3 0
原创粉丝点击