通过Python爬虫爬取知乎某个有关问题下的图片
通过Python爬虫爬取知乎某个问题下的图片
该爬虫的完整代码我把它放到了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 requests # import urllib # import urllib2 import cookielib import re import time import os.path from PIL import Image user_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 captcha def 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 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': 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()登陆进去后,我们在打开某一个知乎问题链接,爬取里面的图片然后下载到本地目录,具体看下面的代码:注意在输入验证码的时候我们用的是input(),在Python2中用input()输入的时候,如果输入字符串,那么要在输入的字符上加上引号,否则会报错,如:“abcd"最后就可以把图片保存到Picture这个目录下了,当然这个爬虫目前还可以做很多的改动,比如翻页功能,然后多线程下载之类的,后续改进后我在贴上来吧。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 def 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 imagesUrl def 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')注:该代码目前只能爬取到知乎某个问题下第一页的回答内容。
=====================更新1:下面来增加爬取知乎时的翻页
知乎网在处理翻页的时候,不像糗事百科这种直接在网址后面加数字一二三就可以实现翻页了,而是向服务器发送post请求,然后服务器响应翻页请求。我们用谷歌开发者工具抓取来看看就清楚了,下面是打开某个问题第一页时的获取情况:
这里我们可以发现,在请求页面的时候,是向服务器请求一个连接,然后post的数据有method和params,然后我们把页面下拉到最下面点击加载更多时如下:
这是第一个QuestionAnswerListV2,是在我点击加载更多的时候产生的,里面的数据内容和第一个的基本一样,只是下面的参数“offset”偏移量不同,增加了10,可以看出在一个页面上显示了10条数据,当然这得是在我们登陆的情况下,没有登陆的话就无法继续往下执行了。
所以到这里我们就已经搞清楚了,在翻页时需要post的链接和data,那处理起来就轻松多了,下面就直接贴代码了,运行后有彩蛋喔,模拟登陆部分还是没变。
注意:下面这段代码虽然可以正常的使用了,但是仍然存在一些问题,因为在爬取的时候,我把Response的网页数据全给存到了list中然后在统一的处理,这就存在一个问题,当list里面的数据太多后,就会导致list溢出,所以在代码中,我给限制了最大的翻页数,这个问题我会在随后的代码中改进,:)
def getImageUrl(pageUrl): pageCode = getNextPage() if not pageCode: print "打开网页链接失败.." return None pattern = re.compile('data-actualsrc="(.*?)">', re.S) imagesUrl = [] for page in pageCode: items = re.findall(pattern, page) for item in items: url = item.replace("\\","") imagesUrl.append(url) return imagesUrl def saveImagesFromUrl(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' def getNextPage(): url = "https://www.zhihu.com/node/QuestionAnswerListV2" method = 'next' size = 10 allPage = [] 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) list = ret['msg'] if not list: return allPage allPage.extend(list) if size == 250: #最多翻页到250页 return allPage login('这是你的知乎密码','这是你的知乎账户') saveImagesFromUrl('/Volumes/HDD/Picture')
效果类似这样: