├── .gitignore ├── Answer.py ├── LICENSE ├── Question.py ├── README.md ├── TimeLine.py ├── User.py ├── Zhuanlan.py ├── config.ini ├── img ├── TimeLine.png ├── help.png ├── login.png ├── zan.png └── zhihu.png ├── login.py ├── requirements.txt └── zhihu.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | 38 | # Cookies file 39 | cookies 40 | 41 | # verify file 42 | *.gif 43 | 44 | # Virtual environment 45 | venv 46 | -------------------------------------------------------------------------------- /Answer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: 1.0 6 | @author: lizheming 7 | @contact: nkdudu@126.com 8 | @site: lizheming.top 9 | @file: Answer.py 10 | """ 11 | 12 | 13 | from zhihu import zhihu, headers, clear, error, session, mul_get_request, mul_post_request 14 | from bs4 import BeautifulSoup, NavigableString 15 | import re 16 | import json 17 | import webbrowser 18 | import termcolor 19 | 20 | 21 | class Answer: 22 | url = None 23 | soup = None 24 | 25 | def __init__(self, url): 26 | self.url = url 27 | self.headers = headers.copy() 28 | self.headers["Referer"] = self.url 29 | 30 | def parse(self): 31 | #res = session.get(self.url, headers=self.headers) 32 | res = mul_get_request(session, self.url, headers=self.headers) 33 | if not res: 34 | return False 35 | #print res.content 36 | self.soup = BeautifulSoup(res.content, "html.parser") 37 | self._xsrf = re.findall(r'name="_xsrf" value="(\S+)"', res.content)[0] 38 | return True 39 | 40 | def check(self): 41 | if not self.soup: 42 | self.parse() 43 | 44 | def open_in_browser(self): 45 | webbrowser.open_new(self.url) 46 | 47 | def get_item(self): 48 | self.check() 49 | item = self.soup.find("div", class_="zm-item-answer zm-item-expanded") 50 | return item 51 | 52 | def get_data_id(self): 53 | item = self.get_item() 54 | data_id = item.get("data-aid") 55 | return data_id 56 | 57 | def get_question(self): 58 | self.check() 59 | zm_title = self.soup.find("h2", class_="zm-item-title") 60 | return zm_title.find("a").get("href") 61 | 62 | def get_author_link(self): 63 | item = self.get_item() 64 | author_tag = item.find("a", class_="author-link") 65 | author_link = author_tag.get("href") if author_tag else None 66 | return author_link 67 | 68 | def get_full_answer(self): 69 | item = self.get_item() 70 | answer = item.find("div", class_="zm-editable-content clearfix") 71 | contents = answer.contents 72 | print "\n", 73 | print_content(contents) 74 | print "\n", 75 | ''' 76 | answer_content = item.find("div", class_="zm-editable-content clearfix").text.strip() 77 | return " " + answer_content 78 | ''' 79 | 80 | def vote_up(self): 81 | url = "https://www.zhihu.com/node/AnswerVoteBarV2" 82 | params = { 83 | "answer_id": self.get_data_id() 84 | } 85 | data = { 86 | "method": "vote_up", 87 | "_xsrf": self._xsrf, 88 | "params": json.dumps(params) 89 | } 90 | #res = session.post(url, data, headers=self.headers) 91 | res = mul_post_request(session, url, headers=self.headers, data=data) 92 | if not res: 93 | return 94 | #print res.content 95 | if not res.json()["r"]: 96 | print termcolor.colored("赞同成功", "blue") 97 | 98 | def vote_cancle(self): 99 | url = "https://www.zhihu.com/node/AnswerVoteBarV2" 100 | params = { 101 | "answer_id": self.get_data_id() 102 | } 103 | data = { 104 | "method": "vote_neutral", 105 | "_xsrf": self._xsrf, 106 | "params": json.dumps(params) 107 | } 108 | #res = session.post(url, data, headers=self.headers) 109 | res = mul_post_request(session, url, self.headers, data=data) 110 | if not res: 111 | return 112 | #print res.content 113 | if not res.json()["r"]: 114 | print termcolor.colored("取消成功", "blue") 115 | 116 | def vote_down(self): 117 | url = "https://www.zhihu.com/node/AnswerVoteBarV2" 118 | params = { 119 | "answer_id": self.get_data_id() 120 | } 121 | data = { 122 | "method": "vote_down", 123 | "_xsrf": self._xsrf, 124 | "params": json.dumps(params) 125 | } 126 | #res = session.post(url, data, headers=self.headers) 127 | res = mul_post_request(session, url, self.headers, data=data) 128 | if not res: 129 | return 130 | print res.content 131 | if not res.json()["r"]: 132 | print termcolor.colored("取消成功", "blue") 133 | 134 | def add_to_collections(self): 135 | ''' 136 | 暂不支持创建新的收藏夹 137 | ''' 138 | url = "https://www.zhihu.com/collections/json?answer_id={}".format(self.get_data_id()) 139 | #res = session.get(url, headers=self.headers) 140 | res = mul_get_request(session, url, headers) 141 | if not res: 142 | return 143 | collections = res.json()["msg"][0] 144 | print "\n", 145 | for i, m in enumerate(collections): 146 | print "{0}:{1} {2}条答案 {3}人关注".format(i, m[1], m[3], m[4]) 147 | print "\n", 148 | 149 | index = -1 150 | length = len(collections) 151 | while not (0 <= index < length): 152 | num = raw_input("请选择收藏夹序号(0-{}), 输入cancle取消操作\n".format(length-1)) 153 | if num == "cancle": 154 | return 155 | try: 156 | index = int(num) 157 | except: 158 | print termcolor.colored("请输入正确的序号", "red") 159 | add_url = "https://www.zhihu.com/collection/add" 160 | data = { 161 | "answer_id": self.get_data_id(), 162 | "_xsrf": self._xsrf, 163 | "favlist_id": str(collections[index][0]) 164 | } 165 | #res = session.post(add_url, data, headers=self.headers) 166 | res = mul_post_request(session, add_url, self.headers, data=data) 167 | if not res: 168 | return 169 | print "\n", 170 | if res.json()["r"] == 0: 171 | print termcolor.colored("收藏成功", "green") 172 | else: 173 | print termcolor.colored("您已经收藏过该答案", "green") 174 | 175 | def operate(self): 176 | if not self.parse(): 177 | return True 178 | self.get_full_answer() 179 | while True: 180 | op = raw_input("Answer$ ") 181 | if op == "voteup": 182 | self.vote_up() 183 | elif op == "votedown": 184 | self.vote_down() 185 | elif op == "votecancle": 186 | self.vote_cancle() 187 | elif op == "collect": 188 | self.add_to_collections() 189 | elif op == "author": 190 | url = self.get_author_link() 191 | if not url: 192 | print termcolor.colored("作者为匿名用户", "green") 193 | else: 194 | from User import User 195 | user = User(zhihu + url) 196 | if user.operate(): 197 | return True 198 | elif op == "question": 199 | from Question import Question 200 | question = Question(zhihu + self.get_question()) 201 | if question.operate(): 202 | return True 203 | elif op == "browser": 204 | self.open_in_browser() 205 | elif op == "pwd": 206 | self.get_full_answer() 207 | elif op == "clear": 208 | clear() 209 | elif op == "break": 210 | break 211 | elif op == "quit": 212 | return True 213 | elif op == "help": 214 | self.help() 215 | else: 216 | error() 217 | 218 | def help(self): 219 | info = "\n" \ 220 | "**********************************************************\n" \ 221 | "**\n" \ 222 | "** author: 查看作者\n" \ 223 | "** question: 查看问题\n" \ 224 | "** voteup: 赞同回答\n" \ 225 | "** votecancle: 取消赞同回答\n" \ 226 | "** votedown: 反对回答\n" \ 227 | "** collect: 收藏回答\n" \ 228 | "** pwd: 查看当前回答\n" \ 229 | "** browser: 在默认浏览器中查看\n" \ 230 | "** clear: 清屏\n" \ 231 | "** break: 返回上级操作目录\n" \ 232 | "** quit: 退出系统\n" \ 233 | "**\n" \ 234 | "**********************************************************\n" 235 | print termcolor.colored(info, "green") 236 | 237 | 238 | def print_content(contents): 239 | for content in contents: 240 | name = content.name 241 | #if not isinstance(content, Tag): 242 | if isinstance(content, NavigableString): 243 | s = str(content) 244 | s = s.replace("\n","") 245 | print s.strip() 246 | else: 247 | if name == "img": 248 | ''' 249 | img = content.find("img") 250 | if img: 251 | print img.get("src") 252 | ''' 253 | print "[图片]" 254 | elif name == "br": 255 | print "" 256 | elif name == "noscript": 257 | continue 258 | elif name == "li": 259 | print "•", 260 | print_content(content.contents) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 egrcc(Lujun Zhao) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Question.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: 1.0 6 | @author: lizheming 7 | @contact: nkdudu@126.com 8 | @site: lizheming.top 9 | @file: Question.py 10 | """ 11 | 12 | from zhihu import zhihu, headers, clear, error, limit, session, mul_get_request, mul_post_request 13 | from User import User 14 | import webbrowser 15 | import re 16 | from bs4 import BeautifulSoup 17 | import termcolor 18 | import json 19 | 20 | 21 | class Question: 22 | url = None 23 | soup = None 24 | flag = True 25 | 26 | def __init__(self, url): 27 | self.url = url 28 | self.headers = headers.copy() 29 | 30 | def parse(self): 31 | #res = session.get(self.url, headers=self.headers) 32 | res = mul_get_request(session, self.url, headers) 33 | if not res: 34 | return False 35 | self.soup = BeautifulSoup(res.content, "html.parser") 36 | #print res.content 37 | self._xsrf = re.findall(r'name="_xsrf" value="(\S+)"', res.content)[0] 38 | return True 39 | 40 | def check(self): 41 | if not self.soup: 42 | self.parse() 43 | 44 | def open_in_browser(self): 45 | webbrowser.open_new(self.url) 46 | 47 | def get_title(self): 48 | self.check() 49 | #title = self.soup.find("h2", class_="zm-item-title zm-editable-content").text.strip() 50 | title = self.soup.find("h2", class_="zm-item-title") 51 | title_content = title.find("span", class_="zm-editable-content").text.strip() 52 | return title_content 53 | 54 | def show_detail(self): 55 | self.check() 56 | detail = self.soup.find("div", id="zh-question-detail") 57 | from Answer import print_content 58 | if detail.find("div", class_="zm-editable-content"): 59 | detail = detail.find("div", class_="zm-editable-content") 60 | elif detail.find("div", class_="zh-summary summary clearfix"): 61 | detail = detail.find("div", class_="zh-summary summary clearfix") 62 | print_content(detail.contents) 63 | #content = detail.text.strip() 64 | #content = self.soup.find("div", class_="zm-editable-content").text.strip() 65 | #print content 66 | 67 | def get_data_resourceid(self): 68 | self.check() 69 | detail = self.soup.find("div", id="zh-question-detail") 70 | resource_id = str(detail.get("data-resourceid")) 71 | return resource_id 72 | 73 | def get_topics(self): 74 | self.check() 75 | tags = self.soup.select("a[class=zm-item-tag]") 76 | return [tag.text.strip() for tag in tags] 77 | 78 | def get_answer_num(self): 79 | self.check() 80 | answer_num_tag = self.soup.find("h3", id="zh-question-answer-num") 81 | answer_num = int(answer_num_tag.get("data-num")) if answer_num_tag else 0 82 | return answer_num 83 | 84 | def get_follower_num(self): 85 | self.check() 86 | follow_num = self.soup.find("div", class_="zg-gray-normal").strong.text 87 | return int(follow_num) 88 | 89 | def show_base_info(self): 90 | #title = self.get_title() 91 | topics = self.get_topics() 92 | answer_num = self.get_answer_num() 93 | follower_num = self.get_follower_num() 94 | #print termcolor.colored(title, "blue") 95 | print termcolor.colored("话题:" + " ".join(topics), "blue") 96 | print termcolor.colored("回答:{0} 关注{1}".format(answer_num, follower_num), "blue") 97 | 98 | def follow_question(self): 99 | self.parse() 100 | button = self.soup.find("button", class_="follow-button zg-follow zg-btn-green") 101 | if not button: 102 | print termcolor.colored("您已经关注了问题:{}".format(self.get_title()), "red") 103 | return 104 | url = "https://www.zhihu.com/node/QuestionFollowBaseV2" 105 | params = { 106 | "question_id": self.get_data_resourceid() 107 | } 108 | data = { 109 | "method": "follow_question", 110 | "_xsrf": self._xsrf, 111 | "params": json.dumps(params) 112 | } 113 | #res = session.post(url, data, headers=self.headers) 114 | res = mul_post_request(session, url, headers, data=data) 115 | if not res: 116 | return 117 | if res.json()["r"] == 0: 118 | print termcolor.colored("关注问题:{}成功".format(self.get_title()), "blue") 119 | 120 | def unfollow_question(self): 121 | self.parse() 122 | button = self.soup.find("button", class_="follow-button zg-unfollow zg-btn-white") 123 | if not button: 124 | print termcolor.colored("您还没有关注问题:{}".format(self.get_title()), "red") 125 | return 126 | url = "https://www.zhihu.com/node/QuestionFollowBaseV2" 127 | params = { 128 | "question_id": self.get_data_resourceid() 129 | } 130 | data = { 131 | "method": "unfollow_question", 132 | "_xsrf": self._xsrf, 133 | "params": json.dumps(params) 134 | } 135 | #res = session.post(url, data, headers=self.headers) 136 | res = mul_post_request(session, url, headers, data=data) 137 | if not res: 138 | return 139 | if res.json()["r"] == 0: 140 | print termcolor.colored("取消关注问题:{}成功".format(self.get_title()), "blue") 141 | 142 | def get_answers(self): 143 | self.check() 144 | answer_num = self.get_answer_num() 145 | if not answer_num: 146 | print termcolor.colored("该问题还没有回答", "magenta") 147 | return 148 | items = self.soup.find_all("div", class_="zm-item-answer zm-item-expanded") 149 | answer_wrap = self.soup.find("div", id="zh-question-answer-wrap") 150 | summarys = answer_wrap.find_all("div", class_="zh-summary summary clearfix") 151 | #mode = re.compile(r"^zm-item-vote-info") 152 | #items = sorted(items, lambda x, y: int(y.find("div", class_=mode).get("data-votecount")) - int(x.find("div", class_=mode).get("data-votecount"))) 153 | count = 0 154 | iter = 0 155 | times = 0 156 | for item in items: 157 | count += 1 158 | summary = summarys[iter] 159 | iter += 1 160 | qaitem = QAItem(item, self._xsrf, summary) 161 | yield qaitem 162 | while count <= answer_num: 163 | url_token = re.findall(r"(\d+)", self.url)[0] 164 | url = "https://www.zhihu.com/node/QuestionAnswerListV2" 165 | pagesize = 10 166 | offset = pagesize * times + 10 167 | params = { 168 | "url_token": url_token, 169 | "pagesize": pagesize, 170 | "offset": offset 171 | } 172 | data = { 173 | "method": "next", 174 | "params": json.dumps(params), 175 | "_xsrf": self._xsrf 176 | } 177 | #res = session.post(url, data, headers=self.headers) 178 | res = mul_post_request(session, url, headers, data=data) 179 | if not res: 180 | return 181 | #print res.content 182 | items = res.json()["msg"] 183 | #print items 184 | for item in items: 185 | count += 1 186 | iitem = BeautifulSoup(item, "html.parser") 187 | qaitem = QAItem(iitem, self._xsrf) 188 | yield qaitem 189 | times += 1 190 | 191 | def answer_operate(self): 192 | answers = self.get_answers() 193 | answer_num = self.get_answer_num() 194 | i = 0 195 | count = 0 196 | answerlist = [] 197 | mode = re.compile(r"^\d+$") 198 | for answer in answers: 199 | if i < limit and count < answer_num: 200 | print count 201 | answer.show_item_info() 202 | answerlist.append(answer) 203 | i += 1 204 | count += 1 205 | if i == limit or count == answer_num: 206 | while True: 207 | op = raw_input("All Answers of Question$ ") 208 | if op == "": 209 | if count == answer_num: 210 | print termcolor.colored("没有更多回答", "red") 211 | else: 212 | i = 0 213 | clear() 214 | break 215 | elif re.match(mode, op.strip()): 216 | opn = int(op) 217 | if opn < len(answerlist): 218 | if answerlist[opn].operate(): 219 | return True 220 | elif op == "pwd": 221 | clear() 222 | start = max(0, count - limit) 223 | for x in xrange(start, count): 224 | print x 225 | answerlist[x].show_item_info() 226 | elif op == "browser": 227 | self.open_in_browser() 228 | elif op == "break": 229 | return False 230 | elif op == "help": 231 | self.help2() 232 | elif op == "quit": 233 | return True 234 | elif op == "clear": 235 | clear() 236 | else: 237 | error() 238 | 239 | def operate(self): 240 | #self.show_base_info() 241 | if not self.parse(): 242 | return True 243 | print termcolor.colored(self.get_title(), "blue") 244 | while True: 245 | op = raw_input("Question$ ") 246 | if op == "follow": 247 | self.follow_question() 248 | elif op == "unfollow": 249 | self.unfollow_question() 250 | elif op == "answers": 251 | if self.answer_operate(): 252 | return True 253 | elif op == "pwd": 254 | print termcolor.colored(self.get_title(), "blue") 255 | elif op == "info": 256 | self.show_base_info() 257 | elif op == "detail": 258 | self.show_detail() 259 | elif op == "browser": 260 | self.open_in_browser() 261 | elif op == "clear": 262 | clear() 263 | elif op == "break": 264 | break 265 | elif op == "help": 266 | self.help() 267 | elif op == "quit": 268 | return True 269 | else: 270 | error() 271 | return False 272 | 273 | def help(self): 274 | info = "\n" \ 275 | "**********************************************************\n" \ 276 | "**\n" \ 277 | "** info: 问题的基本信息\n" \ 278 | "** detail: 问题的描述\n" \ 279 | "** follow: 关注问题\n" \ 280 | "** unfollow: 取消关注问题\n" \ 281 | "** answers: 查看问题的所有回答\n" \ 282 | "** pwd: 显示当前问题\n" \ 283 | "** browser: 在默认浏览器中查看\n" \ 284 | "** clear: 清屏\n" \ 285 | "** break: 返回上级操作目录\n" \ 286 | "** quit: 退出系统\n" \ 287 | "**\n" \ 288 | "**********************************************************\n" 289 | print termcolor.colored(info, "green") 290 | 291 | def help2(self): 292 | info = "\n" \ 293 | "**********************************************************\n" \ 294 | "**\n" \ 295 | "** 回车: 下一页\n" \ 296 | "** #Num.: 选中具体回答条目进行操作(仅限从0到当前最后一条)\n" \ 297 | "** pwd: 显示当前回答页\n" \ 298 | "** browser: 在默认浏览器中查看\n" \ 299 | "** clear: 清屏\n" \ 300 | "** break: 返回上级操作目录\n" \ 301 | "** quit: 退出系统\n" \ 302 | "**\n" \ 303 | "**********************************************************\n" 304 | print termcolor.colored(info, "green") 305 | 306 | 307 | class QAItem: 308 | item = None 309 | 310 | def __init__(self, item, _xsrf, summary=None): 311 | self.item = item 312 | self._xsrf = _xsrf 313 | self.headers = headers.copy() 314 | self.summary = summary 315 | 316 | def get_author_info(self): 317 | author_info = self.item.find("div", class_="zm-item-answer-author-info").text.strip() 318 | author_info = author_info.replace("\n","") 319 | return author_info 320 | 321 | def get_author_link(self): 322 | author_link_tag = self.item.find("a", class_="author-link") 323 | author_link = author_link_tag.get("href") if author_link_tag else None 324 | return author_link 325 | 326 | def get_item_vote_info(self): 327 | mode = re.compile(r"^zm-item-vote-info") 328 | vote_info_tag = self.item.find("div", class_=mode) 329 | vote_info = vote_info_tag.text.strip() 330 | #a = re.findall(r'[ , \n]+', vote_info)[0] 331 | a = re.findall(r'\s+', vote_info) 332 | if a: 333 | vote_info = vote_info.replace(a[0], "") 334 | vote_info = re.sub(r'[\s+\n\t]', '', vote_info) 335 | vote_count = str(vote_info_tag.get("data-votecount")) 336 | if not vote_info: 337 | vote_info += "{}人赞同".format(vote_count) 338 | return vote_info 339 | 340 | def get_answer_summary(self): 341 | if self.summary: 342 | return self.summary.text.strip() 343 | answer_summary = self.item.find("div", class_="zh-summary summary clearfix") 344 | if answer_summary: 345 | return answer_summary.text.strip() 346 | return "" 347 | 348 | def get_answer_link(self): 349 | #answer_link = self.item.find("div", class_="zm-item-rich-text expandable js-collapse-body").get("data-entry-url") 350 | #return answer_link 351 | url = self.item.find("link",itemprop="url") 352 | return url.get("href") 353 | 354 | def get_data_id(self): 355 | data_aid = self.item.get("data-aid") 356 | return str(data_aid) 357 | 358 | def show_item_info(self): 359 | author_info = self.get_author_info() 360 | vote_info = self.get_item_vote_info() 361 | answer_summary = " " + self.get_answer_summary() 362 | #answer_summary = answer_summary.replace("\S+","") 363 | print termcolor.colored(author_info, "green"), 364 | print termcolor.colored("({})".format(vote_info), "white") 365 | print answer_summary 366 | print "\n", 367 | 368 | def vote_up_answer(self): 369 | url = "https://www.zhihu.com/node/AnswerVoteBarV2" 370 | params = { 371 | "answer_id": self.get_data_id() 372 | } 373 | data = { 374 | "method": "vote_up", 375 | "_xsrf": self._xsrf, 376 | "params": json.dumps(params) 377 | } 378 | #res = session.post(url, data, headers=self.headers) 379 | res = mul_post_request(session, url, headers, data=data) 380 | if not res: 381 | return 382 | #print res.content 383 | if res.json()["r"] == 0: 384 | print termcolor.colored("赞同成功", "blue") 385 | 386 | def vote_down_answer(self): 387 | url = "https://www.zhihu.com/node/AnswerVoteBarV2" 388 | params = { 389 | "answer_id": self.get_data_id() 390 | } 391 | data = { 392 | "method": "vote_down", 393 | "_xsrf": self._xsrf, 394 | "params": json.dumps(params) 395 | } 396 | #res = session.post(url, data, headers=self.headers) 397 | res = mul_post_request(session, url, headers, data=data) 398 | if not res: 399 | return 400 | #print res.content 401 | if res.json()["r"] == 0: 402 | print termcolor.colored("取消赞同成功", "blue") 403 | 404 | def vote_cancle_answer(self): 405 | url = "https://www.zhihu.com/node/AnswerVoteBarV2" 406 | params = { 407 | "answer_id": self.get_data_id() 408 | } 409 | data = { 410 | "method": "vote_neutral", 411 | "_xsrf": self._xsrf, 412 | "params": json.dumps(params) 413 | } 414 | #res = session.post(url, data, headers=self.headers) 415 | res = mul_post_request(session, url, headers, data=data) 416 | if not res: 417 | return 418 | #print res.content 419 | if res.json()["r"] == 0: 420 | print termcolor.colored("取消赞同成功", "blue") 421 | 422 | def operate(self): 423 | self.show_item_info() 424 | while True: 425 | op = raw_input("Question Answer Item$ ") 426 | if op == "voteup": 427 | self.vote_up_answer() 428 | elif op == "votedown": 429 | self.vote_down_answer() 430 | elif op == "votecancle": 431 | self.vote_cancle_answer() 432 | elif op == "answer": 433 | from Answer import Answer 434 | answer = Answer(zhihu + self.get_answer_link()) 435 | if answer.operate(): 436 | return True 437 | elif op == "author": 438 | author_link = self.get_author_link() 439 | if author_link: 440 | user = User(zhihu + self.get_author_link()) 441 | if user.operate(): 442 | return True 443 | else: 444 | print termcolor.colored("回答者为匿名用户", "red") 445 | elif op == "pwd": 446 | self.show_item_info() 447 | elif op == "help": 448 | self.help() 449 | elif op == "break": 450 | break 451 | elif op == "clear": 452 | clear() 453 | elif op == "quit": 454 | return True 455 | else: 456 | error() 457 | 458 | def help(self): 459 | info = "\n" \ 460 | "**********************************************************\n" \ 461 | "**\n" \ 462 | "** answer: 查看回答\n" \ 463 | "** author: 查看作者\n" \ 464 | "** voteup: 赞同回答\n" \ 465 | "** votecancle: 取消赞同回答\n" \ 466 | "** votedown: 反对回答\n" \ 467 | "** pwd: 当前问题回答条目\n" \ 468 | "** clear: 清屏\n" \ 469 | "** break: 返回上级操作目录\n" \ 470 | "** quit: 退出系统\n" \ 471 | "**\n" \ 472 | "**********************************************************\n" 473 | print termcolor.colored(info, "green") -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | zhihu-terminal:终端版知乎 2 | =============================== 3 | 4 | ##Author: 5 | 6 | * [lizheming](http://lizheming.top) 7 | 8 | * 9 | 10 | ##介绍 11 | 12 | zhihu-terminal 采用 python2.7编写,通过在命令行中执行python程序运行知乎客户端,就可以用类似bash命令的方式来刷TL、来关注问题和给别人点赞啦。 13 | 14 | 更重要的是以后在实验开着命令行就不会被老板和同学发现我是在刷知乎了哦。。 15 | 16 | 本项目部分代码参考借鉴项目:[zhihu-python]() , 感谢@[egrcc](https://github.com/egrcc)的分享。 17 | 18 | 19 | **本项目代码在Mac OSX 10.10.5上开发及测试,尚未兼容其他系统。** 20 | 21 | ##快速开始 22 | 23 | ### 准备 24 | 25 | **Tips** : 26 | 27 | 建议新建一个虚拟python环境并确保安装的python版本为2.7,以免与系统原有的依赖库出现版本冲突等问题。具体安装虚拟环境的步骤这里不再赘述。 28 | 29 | 30 | **克隆本项目** 31 | 32 | git clone git@github.com:duduainankai/zhihu-terminal.git 33 | cd zhihu-terminal 34 | 35 | 36 | **依赖** 37 | 38 | 本项目依赖于: 39 | 40 | * [Beautiful Soup 4](http://www.crummy.com/software/BeautifulSoup/) 41 | * [requests](https://github.com/kennethreitz/requests) 42 | * [termcolor](https://pypi.python.org/pypi/termcolor) 43 | 44 | 准备好虚拟环境并激活,执行以下命令可直接安装依赖: 45 | 46 | 47 | pip install -r requirements.txt 48 | 49 | 50 | ps. 可以执行以下命令查看是否正确安装 51 | 52 | 53 | pip list 54 | 55 | 56 | 57 | ### 开启终端知乎 58 | 59 | 60 | **填写账号密码** 61 | 62 | 找到文件夹下的config.ini文件,填写登录帐号的邮箱和密码。 63 | 64 | **测试登录** 65 | 66 | python login.py 67 | 68 | login.py的代码实现参考的就是[zhihu-python](https://github.com/egrcc/zhihu-python)。 69 | 70 | 如果不出意外的话应该就可以得到下面的结果了。 71 | 72 | ![](/img/login.png) 73 | 74 | **体验终端版知乎** 75 | 76 | 77 | python zhihu.py 78 | 79 | 正确登录之后执行上面的命令,应该就可以看到你自己的TL了(下面这图是我的)。 80 | 81 | ![](/img/TimeLine.png) 82 | 83 | **操作目录** 84 | 85 | 我所定义的操作目录类似于linux下的工作目录(可以通过pwd查看),操作目录可以分为Time Line、Question、Answer、User等,可以看到在输入命令的前面提示的就是当前所属的操作目录,同样也可以通过pwd查看更详细的信息。 86 | 87 | **用命令来点赞** 88 | 89 | 在每一个操作目录下都可以执行特定的操作,比如给一个答案点赞、关注一个用户或问题等等,当然你不需要像背shell命令一样记下所有的操作,在每个操作目录下输入help就可以查看当前可以执行的命令及其解释了。 90 | 91 | ![](/img/help.png) 92 | 93 | 然后我就可以用"voteup"给这位仁兄的答案点赞了。 94 | 95 | ![](/img/zan.png) 96 | 97 | 查看一下知乎的动态 98 | 99 | ![](/img/zhihu.png) 100 | 101 | 现在提供的功能还比较基本,可以先尝试着玩一下。有时间的话应该还会扩展的。 102 | 103 | **轮带逛系列** 104 | 105 | 当然有些回答精华都在图片中(就像轮子哥带我们逛过的那些),但是终端中目前还没找到太好的显示方法(有哪位大神有办法的话还望不吝赐教),因此为了不扫大家的兴可以用"browser"命令在默认的浏览器中打开知乎,查看当前所在操作目录的内容。当然还是希望尽量多用这个终端版的知乎咯,毕竟开着命令行可以掩人耳目,让老板以为是在认真学习而不会想到是在刷知乎。 106 | 107 | **欢迎使用** 108 | 109 | 欢迎克隆使用终端版知乎,能给个star当然就更好咯。 110 | 111 | 有任何问题、建议或者bug也希望能联系我,谢谢。 112 | 113 | 联系我 114 | ---------- 115 | 116 | - email: nkdudu@126.com 117 | - github: https://github.com/duduainankai 118 | - zhihu: https://www.zhihu.com/people/du-du-76-75 -------------------------------------------------------------------------------- /TimeLine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: 1.0 6 | @author: lizheming 7 | @contact: nkdudu@126.com 8 | @site: lizheming.top 9 | @file: TimeLine.py 10 | """ 11 | 12 | import re 13 | import termcolor 14 | from zhihu import zhihu, error, clear 15 | 16 | 17 | class TLItem: 18 | item = None 19 | 20 | def __init__(self, item, _xsrf): 21 | self.item = item 22 | self._xsrf = _xsrf 23 | 24 | def get_id(self): 25 | item = self.item 26 | tid = item.get("id") 27 | tid = tid[tid.find("-")+1:] 28 | return tid 29 | 30 | def get_feed_type(self): 31 | item = self.item 32 | feedtype = item.get("data-feedtype") 33 | return feedtype 34 | 35 | def get_source(self): 36 | source = self.item.find("div", class_="feed-source") 37 | return source 38 | 39 | def get_content(self): 40 | content = self.item.find("div", class_="feed-content") 41 | return content 42 | 43 | def get_rich_text(self): 44 | item = self.item 45 | item_rich_text = item.find("div", class_="zm-item-rich-text expandable js-collapse-body") 46 | return item_rich_text 47 | 48 | def get_resource_id(self): 49 | item_rich_text = self.get_rich_text() 50 | tid = self.get_id() 51 | resource_id = item_rich_text.get("data-resourceid") if item_rich_text else tid[1:tid.find("_")] 52 | return resource_id 53 | 54 | def get_titles(self): 55 | source = self.get_source() 56 | title = source.text.strip() 57 | title = title.replace("\n", " ") 58 | titles = re.split(r'\s+', title) 59 | return titles 60 | 61 | def get_create_time(self): 62 | titles = self.get_titles() 63 | create_time = ''.join(titles[-2:]) 64 | return create_time 65 | 66 | def get_vote_number(self): 67 | item = self.item 68 | vote = item.find("span", class_="count") 69 | vote_number = "(" + vote.text + "赞)" if vote else None 70 | return vote_number 71 | 72 | def get_feed_title(self): 73 | feedtype = self.get_feed_type() 74 | source = self.get_source() 75 | titles = self.get_titles() 76 | vote_number = self.get_vote_number() 77 | links = source.find_all("a", class_="zg-link author-link") 78 | member = links[0].text + ''.join(("、" + a.text) for a in links[1:]) 79 | create_time = self.get_create_time() 80 | source_type = titles[-3] + vote_number if vote_number else titles[-3] 81 | if feedtype.find("ARTICLE_VOTE_UP") != -1: 82 | zone = source.find("a", class_="question_link") 83 | zl = "" 84 | if zone: 85 | zl = "赞了{}".format(zone.text) 86 | feed_title = termcolor.colored(member + " ", "red") + termcolor.colored(zl + source_type + ":\t" + create_time, "white") 87 | elif feedtype.find("ARTICLE_CREATE") != -1: 88 | zone = source.find("a", class_="question_link") 89 | zl = "" 90 | if zone: 91 | zl = "在{}".format(zone.text) 92 | feed_title = termcolor.colored(member + " ", "red") + termcolor.colored(zl + source_type + ":\t" + create_time, "white") 93 | else: 94 | feed_title = termcolor.colored(member + " ", "red") + termcolor.colored(source_type + ":\t" + create_time, "white") 95 | return feed_title 96 | 97 | def get_feed_title_another(self): 98 | source = self.get_source().text 99 | feed_title = source.replace("\n", "") 100 | feed_title = feed_title.replace("\s+"," ") 101 | return feed_title 102 | 103 | def get_question_info(self): 104 | content = self.get_content() 105 | question = content.select("a")[0] 106 | question_link = zhihu + question.get("href") 107 | question_content = termcolor.colored(question.text, "cyan") 108 | question_content = question_content.replace("\n", "") 109 | return question_link, question_content 110 | 111 | def get_answer_link(self): 112 | item_rich_text = self.get_rich_text() 113 | url = None 114 | if item_rich_text: 115 | url = zhihu + item_rich_text.get("data-entry-url") 116 | return url 117 | 118 | def get_answer_info(self): 119 | content = self.get_content() 120 | zhsummary = content.findAll("div", class_="zh-summary summary clearfix") 121 | answer_summary = " " + ''.join(a.text.strip() for a in zhsummary) if zhsummary else None 122 | ahref = zhsummary[0].find("a", class_="toggle-expand") if zhsummary else None 123 | answer_link = None 124 | if ahref: 125 | answer_link = ahref["href"] 126 | if answer_link.find("zhuanlan") == -1: 127 | answer_link = zhihu + answer_link 128 | return answer_link, answer_summary 129 | 130 | def get_author_info(self): 131 | content = self.get_content() 132 | feedtype = self.get_feed_type() 133 | author_link = None 134 | author_name = None 135 | if feedtype == "ANSWER_VOTE_UP": 136 | author_tag = content.find("div", class_="zm-item-answer-author-info") 137 | if author_tag: 138 | author = author_tag.find("a", class_="author-link") 139 | if author: 140 | author_link = zhihu + author.get("href") 141 | author_name = author.text 142 | else: 143 | author_name = "匿名用户" 144 | elif feedtype == "ANSWER_CREATE": 145 | source = self.get_source() 146 | author = source.find("a") 147 | author_link = author.get("href") 148 | author_name = author.text 149 | return author_link, author_name 150 | 151 | def get_zhuanlan_link(self): 152 | content = self.get_content() 153 | zhuanlan_link = content.find("a", class_="post-link").get("href") 154 | return zhuanlan_link 155 | 156 | def get_item_info(self): 157 | tid = self.get_id() 158 | resource_id = self.get_resource_id() 159 | feed_title = self.get_feed_title() 160 | question_link, question_content = self.get_question_info() 161 | answer_link, answer_summary = self.get_answer_info() 162 | author_link, author_name = self.get_author_info() 163 | answer_link = self.get_answer_link() 164 | if author_name: 165 | question_content += termcolor.colored("(" + author_name + ")", "green") 166 | return tid, [feed_title, question_content, answer_summary], [question_link, answer_link, author_link, resource_id] 167 | 168 | def operate(self): 169 | d = self.get_item_info() 170 | ftype = self.get_feed_type() 171 | print "\n".join(i for i in d[1] if i) + "\n" 172 | while True: 173 | global flag 174 | op = raw_input("Time Line Item$ ") 175 | if op == "answer": 176 | if ftype.startswith("ANSWER"): 177 | #print d[2][1] 178 | from Answer import Answer 179 | answer = Answer(d[2][1]) 180 | #print answer.get_full_answer() 181 | if answer.operate(): 182 | flag = False 183 | return True 184 | else: 185 | error() 186 | elif op == "question": 187 | if ftype.startswith("ANSWER") or ftype.startswith("QUESTION"): 188 | #print d[2][0] 189 | from Question import Question 190 | question = Question(d[2][0]) 191 | if question.operate(): 192 | flag = False 193 | return True 194 | else: 195 | error() 196 | elif op == "author": 197 | if d[2][2]: 198 | #print d[2][2] 199 | from User import User 200 | user = User(d[2][2], self._xsrf) 201 | if user.operate(): 202 | flag = False 203 | return True 204 | else: 205 | error() 206 | elif op == "zhuanlan": 207 | if ftype.find("ARTICLE") != -1: 208 | url = self.get_zhuanlan_link() 209 | from Zhuanlan import Zhuanlan 210 | zhuanlan = Zhuanlan(url) 211 | if zhuanlan.operate(): 212 | flag = False 213 | return True 214 | else: 215 | error() 216 | elif op == "pwd": 217 | print "\n".join(i for i in d[1] if i) + "\n" 218 | elif op == "help": 219 | self.help() 220 | elif op == "break": 221 | break 222 | elif op == "clear": 223 | clear() 224 | elif op == "quit": 225 | flag = False 226 | return True 227 | else: 228 | error() 229 | 230 | def help(self): 231 | info = "\n" \ 232 | "*************************************************************************\n" \ 233 | "**\n" \ 234 | "** answer: 查看回答(仅当TL条目与回答相关时)\n" \ 235 | "** author: 查看回答的作者(仅当TL条目与回答相关时)\n" \ 236 | "** question: 查看问题(仅当TL条目与回答或问题相关时)\n" \ 237 | "** zhuanlan: 查看专栏(仅当TL条目与专栏相关时)\n" \ 238 | "** pwd: 查看当前TL条目内容\n" \ 239 | "** clear: 清屏\n" \ 240 | "** break: 返回上级操作目录\n" \ 241 | "** quit: 退出系统\n" \ 242 | "**\n" \ 243 | "************************************************************************\n" 244 | print termcolor.colored(info, "green") -------------------------------------------------------------------------------- /User.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: 1.0 6 | @author: lizheming 7 | @contact: nkdudu@126.com 8 | @site: lizheming.top 9 | @file: User.py 10 | """ 11 | 12 | from zhihu import zhihu, headers, clear, error, limit, session, mul_post_request, mul_get_request 13 | import webbrowser 14 | from bs4 import BeautifulSoup 15 | import re 16 | import termcolor 17 | import json 18 | 19 | 20 | class User: 21 | url = None 22 | soup = None 23 | 24 | def __init__(self, url, _xsrf=None): 25 | self.url = url 26 | self.headers = headers.copy() 27 | self._xsrf = _xsrf 28 | #self.headers["Referer"] = self.url 29 | 30 | def parse(self): 31 | url = self.url 32 | #res = session.get(url, headers=self.headers) 33 | res = mul_get_request(session, url, headers) 34 | if not res: 35 | return False 36 | self.soup = BeautifulSoup(res.content, "html.parser") 37 | self.profile_header = self.soup.find("div", class_="zm-profile-header ProfileCard") 38 | # self._xsrf = re.findall(r'name="_xsrf" value="(\S+)"', res.content)[0] 39 | return True 40 | 41 | def check(self): 42 | if not self.soup: 43 | self.parse() 44 | 45 | def open_in_browser(self): 46 | webbrowser.open_new(self.url) 47 | 48 | def get_title_tag(self): 49 | self.check() 50 | title = self.soup.find("h1", class_="ProfileHeader-title") 51 | return title 52 | 53 | def get_name(self): 54 | title = self.get_title_tag() 55 | return title.find("span", class_="ProfileHeader-name").text.strip() 56 | 57 | def get_title(self): 58 | title = self.get_title_tag() 59 | name = self.get_name() 60 | des = title.find("span", class_="RichText ProfileHeader-headline").text.strip() 61 | return name + des 62 | 63 | def get_weibo(self): 64 | self.check() 65 | weibo_header = self.profile_header.find("a", class_="zm-profile-header-user-weibo") 66 | weibo = weibo_header.get("href") if weibo_header else None 67 | return weibo 68 | 69 | def open_weibo(self): 70 | weibo = self.get_weibo() 71 | if weibo: 72 | webbrowser.open_new(weibo) 73 | else: 74 | print termcolor.colored("该用户没有绑定微博", "magenta") 75 | 76 | def get_education(self): 77 | self.check() 78 | education_item = self.profile_header.find("span", class_="education item") 79 | education = education_item.get("title") if education_item else None 80 | return education 81 | 82 | def get_employment(self): 83 | self.check() 84 | employment_item = self.profile_header.find("span", class_="employment item") 85 | employment = employment_item.get("title") if employment_item else None 86 | return employment 87 | 88 | def get_agrees(self): 89 | self.check() 90 | agrees = re.findall(r"(\d+)", self.soup.find("div", class_="IconGraf").text)[0] 91 | return agrees 92 | 93 | def get_thanks(self): 94 | self.check() 95 | thanks = re.findall(r"(\d+)", self.soup.find("div", class_="Profile-sideColumnItemValue").text)[0] 96 | return thanks 97 | 98 | def get_behavior_numbers(self): 99 | self.check() 100 | numbers = self.profile_header.find_all("span", class_="num") 101 | return [int(number.text) for number in numbers] 102 | 103 | def get_following_numbers(self): 104 | self.check() 105 | follow_numbers = self.soup.find_all("div", class_="NumberBoard-value") 106 | return [int(number.text) for number in follow_numbers] 107 | 108 | def show_base_info(self): 109 | infos = list() 110 | title = self.get_title() 111 | infos.append(termcolor.colored(title, "magenta")) 112 | # education = self.get_education() 113 | # infos.append(termcolor.colored("教育经历:" + education, "magenta") if education else education) 114 | # employment = self.get_employment() 115 | # infos.append(termcolor.colored("工作经历:" + employment, "magenta") if employment else employment) 116 | # thanks = self.get_thanks() 117 | # agrees = self.get_agrees() 118 | # infos.append(termcolor.colored("获得{0}赞同 {1}感谢".format(agrees, thanks), "magenta")) 119 | following_numbers = self.get_following_numbers() 120 | infos.append(termcolor.colored("关注了{0} 关注者{1}".format(*following_numbers), "magenta")) 121 | # numbers = self.get_behavior_numbers() 122 | # infos.append(termcolor.colored("提问{0} 回答{1} 文章{2} 收藏{3} 公共编辑{4}".format(*numbers), "magenta")) 123 | info = "\n".join(x for x in infos if x) 124 | print info 125 | 126 | def get_answers(self): 127 | self.check() 128 | answer_num = int(self.soup.find("span", class_="Tabs-meta").text) 129 | for page in range(answer_num / 20 + 1): 130 | url = self.url + "/answers" 131 | params = { 132 | "order_by": "vote_num", 133 | "page": page + 1 134 | } 135 | #res = session.get(url, params=params, headers=self.headers) 136 | res = mul_get_request(session, url, headers, params=params) 137 | if not res: 138 | return 139 | soup = BeautifulSoup(res.content, "html.parser") 140 | zmitems = soup.select("div[class=zm-item]") 141 | for item in zmitems: 142 | yield UAItem(item, self._xsrf, self.get_name()) 143 | 144 | def follow_member(self): 145 | self.parse() 146 | username = self.get_name() 147 | #follow_button = self.soup.find("button", class_="zg-btn zg-btn-follow zm-rich-follow-btn with-icon") 148 | follow_button = self.soup.find("button", class_="Button FollowButton Button--primary Button--blue") 149 | unfollow_button = self.soup.find("button", class_="Button FollowButton Button--primary Button--grey") 150 | if not follow_button and unfollow_button: 151 | print termcolor.colored("您已经关注了用户{}".format(username), "red") 152 | return 153 | data_id = str(follow_button.get("data-id")) 154 | url = "https://www.zhihu.com/node/MemberFollowBaseV2" 155 | params = { 156 | "hash_id": data_id 157 | } 158 | data = { 159 | "method": "follow_member", 160 | "params": json.dumps(params), 161 | "_xsrf": self._xsrf 162 | } 163 | #res = session.post(url, data=data, headers=self.headers) 164 | res = mul_post_request(session, url, headers, data=data) 165 | if not res: 166 | return 167 | if not res.json()["r"]: 168 | print termcolor.colored("关注用户{}成功".format(username), "blue") 169 | 170 | def unfollow_member(self): 171 | self.parse() 172 | username = self.get_title().split(",")[0].strip() #中文的逗号 173 | #unfollow_button = self.soup.find("button", class_="zg-btn zg-btn-unfollow zm-rich-follow-btn with-icon") 174 | follow_button = self.soup.find("button", class_="Button FollowButton Button--primary Button--blue") 175 | unfollow_button = self.soup.find("button", class_="Button FollowButton Button--primary Button--grey") 176 | if not unfollow_button and follow_button: 177 | print termcolor.colored("您还没有关注用户{0}".format(username), "red") 178 | return 179 | data_id = str(unfollow_button.get("data-id")) 180 | url = "https://www.zhihu.com/node/MemberFollowBaseV2" 181 | params = { 182 | "hash_id": data_id 183 | } 184 | data = { 185 | "method": "unfollow_member", 186 | "params": json.dumps(params), 187 | "_xsrf": self._xsrf 188 | } 189 | #res = session.post(url, data=data, headers=self.headers) 190 | res = mul_post_request(session, url, headers, data=data) 191 | if not res: 192 | return 193 | if not res.json()["r"]: 194 | print termcolor.colored("取消关注用户{}成功".format(username), "blue") 195 | 196 | def answer_operate(self): 197 | print "\n", 198 | answers = self.get_answers() 199 | answer_num = int(self.soup.find("span", class_="Tabs-meta").text) 200 | i = 0 201 | count = 0 202 | answerlist = [] 203 | mode = re.compile(r"^\d+$") 204 | for answer in answers: 205 | if i < limit and count != answer_num: 206 | print count 207 | answer.show_item_info() 208 | answerlist.append(answer) 209 | i += 1 210 | count += 1 211 | if i == limit or count == answer_num: 212 | while True: 213 | op = raw_input("{}\'s All Answers$ ".format(self.get_name())) 214 | if op == "": 215 | if count == answer_num: 216 | print termcolor.colored("没有更多回答", "red") 217 | else: 218 | clear() 219 | i = 0 220 | break 221 | elif re.match(mode, op.strip()): 222 | opn = int(op) 223 | if opn < len(answerlist): 224 | if answerlist[opn].operate(): 225 | return True 226 | else: 227 | print termcolor.colored("请输入正确的序号", "red") 228 | elif op == "pwd": 229 | clear() 230 | start = max(0, count - limit) 231 | for x in xrange(start, count): 232 | print x 233 | answerlist[x].show_item_info() 234 | elif op == "browser": 235 | self.open_in_browser() 236 | elif op == "break": 237 | return False 238 | elif op == "help": 239 | self.help2() 240 | elif op == "quit": 241 | return True 242 | elif op == "clear": 243 | clear() 244 | else: 245 | error() 246 | 247 | def operate(self): 248 | if not self.parse(): 249 | return True 250 | self.show_base_info() 251 | while True: 252 | op = raw_input("User$ ") 253 | if op == "follow": 254 | self.follow_member() 255 | elif op == "unfollow": 256 | self.unfollow_member() 257 | elif op == "answers": 258 | if self.answer_operate(): 259 | return True 260 | elif op == "pwd": 261 | self.show_base_info() 262 | elif op == "browser": 263 | self.open_in_browser() 264 | elif op == "break": 265 | break 266 | elif op == "help": 267 | self.help() 268 | elif op == "clear": 269 | clear() 270 | elif op == "quit": 271 | return True 272 | else: 273 | error() 274 | 275 | def help(self): 276 | info = "\n" \ 277 | "**********************************************************\n" \ 278 | "**\n" \ 279 | "** follow: 关注用户\n" \ 280 | "** unfollow: 取消关注用户\n" \ 281 | "** answers: 查看用户的回答\n" \ 282 | "** pwd: 查看用户\n" \ 283 | "** browser: 在默认浏览器中查看用户\n" \ 284 | "** break: 返回上级操作目录\n" \ 285 | "** clear: 清屏\n" \ 286 | "** quit: 退出系统\n" \ 287 | "**\n" \ 288 | "**********************************************************\n" 289 | print termcolor.colored(info, "green") 290 | 291 | def help2(self): 292 | info = "\n" \ 293 | "**********************************************************\n" \ 294 | "**\n" \ 295 | "** #Num.: 选中具体用户回答条目查看(仅限从0到当前最后一条)\n" \ 296 | "** 回车: 下一页\n" \ 297 | "** pwd: 当前页\n" \ 298 | "** browser: 在默认浏览器中查看用户回答\n" \ 299 | "** break: 返回上级操作目录\n" \ 300 | "** clear: 清屏\n" \ 301 | "** quit: 退出系统\n" \ 302 | "**\n" \ 303 | "**********************************************************\n" 304 | print termcolor.colored(info, "green") 305 | 306 | 307 | class UAItem: 308 | item = None 309 | 310 | def __init__(self, item, _xsrf, username): 311 | self.item = item 312 | self._xsrf = _xsrf 313 | self.username = username 314 | 315 | def get_vote_up_count(self): 316 | vote_up_count = self.item.find("a", class_="zm-item-vote-count js-expand js-vote-count").text 317 | return vote_up_count 318 | 319 | def get_question_content(self): 320 | question = self.item.find("a", class_="question_link") 321 | question_content = question.text.strip() 322 | return question_content 323 | 324 | def get_answer_link(self): 325 | question = self.item.find("a", class_="question_link") 326 | answer_link = str(question.get("href")) 327 | return answer_link 328 | 329 | def get_question_link(self): 330 | answer_link = self.get_answer_link() 331 | question_link = answer_link[:answer_link.find("answer")] 332 | return question_link 333 | 334 | def get_summary(self): 335 | summary = "" 336 | zhsummary = self.item.find("div", class_="zh-summary summary clearfix") 337 | if zhsummary: 338 | summary = zhsummary.text.strip() 339 | return summary 340 | 341 | def show_item_info(self): 342 | question_content = self.get_question_content() 343 | print termcolor.colored(question_content, "blue") + termcolor.colored("({}赞)".format(self.get_vote_up_count()), "white") 344 | print " " + self.get_summary() 345 | print "\n", 346 | 347 | def get_data_id(self): 348 | item_answer = self.item.find("div", class_="zm-item-answer") 349 | return str(item_answer.get("data-aid")) 350 | 351 | def vote_up(self): 352 | url = "https://www.zhihu.com/node/AnswerVoteBarV2" 353 | params = { 354 | "answer_id": self.get_data_id() 355 | } 356 | data = { 357 | "method": "vote_up", 358 | "_xsrf": self._xsrf, 359 | "params": json.dumps(params) 360 | } 361 | #res = session.post(url, data) 362 | res = mul_post_request(session, url, headers, data=data) 363 | if not res: 364 | return 365 | #print res.content 366 | if not res.json()["r"]: 367 | print termcolor.colored("赞同成功", "blue") 368 | 369 | def vote_cancle(self): 370 | url = "https://www.zhihu.com/node/AnswerVoteBarV2" 371 | params = { 372 | "answer_id": self.get_data_id() 373 | } 374 | data = { 375 | "method": "vote_neutral", 376 | "_xsrf": self._xsrf, 377 | "params": json.dumps(params) 378 | } 379 | #res = session.post(url, data) 380 | res = mul_post_request(session, url, headers, data=data) 381 | if not res: 382 | return 383 | #print res.content 384 | if not res.json()["r"]: 385 | print termcolor.colored("取消成功", "blue") 386 | 387 | def vote_down(self): 388 | url = "https://www.zhihu.com/node/AnswerVoteBarV2" 389 | params = { 390 | "answer_id": self.get_data_id() 391 | } 392 | data = { 393 | "method": "vote_down", 394 | "_xsrf": self._xsrf, 395 | "params": json.dumps(params) 396 | } 397 | #res = session.post(url, data) 398 | res = mul_post_request(session, url, headers, data=data) 399 | if not res: 400 | return 401 | #print res.content 402 | if not res.json()["r"]: 403 | print termcolor.colored("反对成功", "blue") 404 | 405 | def operate(self): 406 | self.show_item_info() 407 | while True: 408 | op = raw_input("{}\'s Answer Item$ ".format(self.username)) 409 | if op == "voteup": 410 | self.vote_up() 411 | elif op =="votecancle": 412 | self.vote_cancle() 413 | elif op == "votedown": 414 | self.vote_down() 415 | elif op == "answer": 416 | answer_link = self.get_answer_link() 417 | from Answer import Answer 418 | answer = Answer(zhihu + answer_link) 419 | if answer.operate(): 420 | return True 421 | elif op == "question": 422 | from Question import Question 423 | question = Question(zhihu + self.get_question_link()) 424 | if question.operate(): 425 | return True 426 | elif op == "pwd": 427 | self.show_item_info() 428 | elif op == "quit": 429 | return True 430 | elif op == "help": 431 | self.help() 432 | elif op == "clear": 433 | clear() 434 | elif op == "break": 435 | break 436 | else: 437 | error() 438 | 439 | def help(self): 440 | info = "\n" \ 441 | "**********************************************************\n" \ 442 | "**\n" \ 443 | "** answer: 查看回答\n" \ 444 | "** question: 查看问题\n" \ 445 | "** voteup: 赞同回答\n" \ 446 | "** votecancle: 取消赞同回答\n" \ 447 | "** votedown: 反对回答\n" \ 448 | "** pwd: 当前用户回答条目\n" \ 449 | "** clear: 清屏\n" \ 450 | "** break: 返回上级操作目录\n" \ 451 | "** quit: 退出系统\n" \ 452 | "**\n" \ 453 | "**********************************************************\n" 454 | print termcolor.colored(info, "green") -------------------------------------------------------------------------------- /Zhuanlan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: 1.0 6 | @author: lizheming 7 | @contact: nkdudu@126.com 8 | @site: lizheming.top 9 | @file: Zhuanlan.py 10 | """ 11 | 12 | from zhihu import headers, clear, error, session 13 | from bs4 import BeautifulSoup 14 | import re 15 | import webbrowser 16 | import termcolor 17 | import requests 18 | import json 19 | import sys 20 | 21 | 22 | class Zhuanlan: 23 | url = None 24 | zhuanlan = None 25 | soup = None 26 | originalurl = None 27 | 28 | def __init__(self, url): 29 | #https://zhuanlan.zhihu.com/p/20825292 30 | self.originalurl = url 31 | number = re.findall(r"(\d+)", url)[0] 32 | self.url = "http://zhuanlan.zhihu.com/api/posts/" + str(number) 33 | 34 | self.headers = headers.copy() 35 | self.headers["Host"] = "zhuanlan.zhihu.com" 36 | 37 | def parse(self): 38 | self.se = requests.Session() 39 | for cookie in session.cookies: 40 | self.se.cookies.set_cookie(cookie) 41 | n = 3 42 | res = None 43 | while n > 0: 44 | try: 45 | res = self.se.get(self.url, headers=self.headers, timeout=30) 46 | break 47 | except: 48 | n -= 1 49 | return False 50 | if not res: 51 | print termcolor.colored("网络故障,请检查您的网络设置", "red") 52 | sys.exit() 53 | self.zhuanlan = dict(res.json()) 54 | self.soup = BeautifulSoup(self.zhuanlan["content"], "html.parser") 55 | return True 56 | 57 | def open_in_browser(self): 58 | webbrowser.open_new(self.originalurl) 59 | 60 | def check(self): 61 | if not self.soup: 62 | self.parse() 63 | 64 | def get_title(self): 65 | self.check() 66 | return termcolor.colored(self.zhuanlan["title"], "blue") 67 | 68 | def get_content(self): 69 | self.check() 70 | from Answer import print_content 71 | print_content(self.soup.contents) 72 | 73 | def get_author_info(self): 74 | self.check() 75 | author = dict(self.zhuanlan["author"]) 76 | return author["profileUrl"] 77 | 78 | def vote(self, type=1): 79 | self.check() 80 | url = self.url + "/rating" 81 | data = {} 82 | if type == 1: 83 | data["value"] = "none" 84 | try: 85 | self.se.put(url, json.dumps(data), headers=self.headers, timeout=15) 86 | except: 87 | print termcolor.colored("网络故障,请检查您的网络设置", "yellow") 88 | return 89 | data["value"] = "like" 90 | else: 91 | data["value"] = "none" 92 | self.headers['Content-Type'] = "application/json;charset=UTF-8" 93 | self.headers["Referer"] = self.originalurl 94 | self.headers["Origin"] = "https://zhuanlan.zhihu.com" 95 | self.headers['X-XSRF-TOKEN'] = self.se.cookies['XSRF-TOKEN'] 96 | try: 97 | res = self.se.put(url, json.dumps(data), headers=self.headers, timeout=15) 98 | except: 99 | print termcolor.colored("网络故障,请检查您的网络设置", "yellow") 100 | return 101 | if res.status_code == 204: 102 | s = "取消赞同成功" if data["value"] == "none" else "赞同成功" 103 | print termcolor.colored(s, "blue") 104 | elif res.status_code == 404: 105 | s = "还没有赞同过" if data["value"] == "none" else "已经赞同过了" 106 | print termcolor.colored(s, "blue") 107 | 108 | def operate(self): 109 | if not self.parse(): 110 | return True 111 | print self.get_title() 112 | while True: 113 | op = raw_input("zhuanlan$ ") 114 | if op == "content": 115 | self.get_content() 116 | elif op == "author": 117 | url = self.get_author_info() 118 | if not url: 119 | print termcolor.colored("当前用户为匿名用户", "red") 120 | else: 121 | from User import User 122 | user = User(url) 123 | if user.operate(): 124 | return True 125 | elif op == "voteup": 126 | self.vote(type=1) 127 | elif op == "votecancle": 128 | self.vote(type=2) 129 | elif op == "pwd": 130 | print self.get_title() 131 | elif op == "browser": 132 | self.open_in_browser() 133 | elif op == "clear": 134 | clear() 135 | elif op == "break": 136 | break 137 | elif op == "help": 138 | self.help() 139 | elif op == "quit": 140 | return True 141 | else: 142 | error() 143 | 144 | def help(self): 145 | info = "\n" \ 146 | "**********************************************************\n" \ 147 | "**\n" \ 148 | "** content: 查看内容\n" \ 149 | "** author: 查看作者\n" \ 150 | "** voteup: 赞同\n" \ 151 | "** votecancle: 取消赞同\n" \ 152 | "** pwd: 显示当前专栏\n" \ 153 | "** browser: 在默认浏览器中查看\n" \ 154 | "** break: 返回上级操作目录\n" \ 155 | "** clear: 清屏\n" \ 156 | "** quit: 退出系统\n" \ 157 | "**\n" \ 158 | "**********************************************************\n" 159 | print termcolor.colored(info, "green") -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [account] 2 | mail= 3 | password= 4 | -------------------------------------------------------------------------------- /img/TimeLine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhemingi/zhihu-terminal/cd57efc606138eee84385a6425998814a0029043/img/TimeLine.png -------------------------------------------------------------------------------- /img/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhemingi/zhihu-terminal/cd57efc606138eee84385a6425998814a0029043/img/help.png -------------------------------------------------------------------------------- /img/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhemingi/zhihu-terminal/cd57efc606138eee84385a6425998814a0029043/img/login.png -------------------------------------------------------------------------------- /img/zan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhemingi/zhihu-terminal/cd57efc606138eee84385a6425998814a0029043/img/zan.png -------------------------------------------------------------------------------- /img/zhihu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhemingi/zhihu-terminal/cd57efc606138eee84385a6425998814a0029043/img/zhihu.png -------------------------------------------------------------------------------- /login.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | 登录文件 6 | 采用https://github.com/egrcc/zhihu-python/blob/master/auth.py的实现 7 | 增加headers验证 8 | 暂时取代login.py v1.0的实现,因为会出现报告登录频繁的错误,待解决. 9 | """ 10 | 11 | import requests 12 | import termcolor 13 | import cookielib 14 | import json 15 | import re 16 | import sys 17 | import os 18 | import platform 19 | import time 20 | from getpass import getpass 21 | 22 | 23 | session = requests.Session() 24 | session.cookies = cookielib.LWPCookieJar('cookies') 25 | try: 26 | session.cookies.load(ignore_discard=True) 27 | except: 28 | pass 29 | 30 | class Logging: 31 | flag = True 32 | 33 | @staticmethod 34 | def error(msg): 35 | if Logging.flag == True: 36 | print "".join( [ termcolor.colored("ERROR", "red"), ": ", termcolor.colored(msg, "white") ] ) 37 | @staticmethod 38 | def warn(msg): 39 | if Logging.flag == True: 40 | print "".join( [ termcolor.colored("WARN", "yellow"), ": ", termcolor.colored(msg, "white") ] ) 41 | @staticmethod 42 | def info(msg): 43 | # attrs=['reverse', 'blink'] 44 | if Logging.flag == True: 45 | print "".join( [ termcolor.colored("INFO", "magenta"), ": ", termcolor.colored(msg, "white") ] ) 46 | @staticmethod 47 | def debug(msg): 48 | if Logging.flag == True: 49 | print "".join( [ termcolor.colored("DEBUG", "magenta"), ": ", termcolor.colored(msg, "white") ] ) 50 | @staticmethod 51 | def success(msg): 52 | if Logging.flag == True: 53 | print "".join( [ termcolor.colored("SUCCES", "green"), ": ", termcolor.colored(msg, "white") ] ) 54 | 55 | # Setting Logging 56 | Logging.flag = True 57 | 58 | class LoginPasswordError(Exception): 59 | def __init__(self, message): 60 | if type(message) != type("") or message == "": self.message = u"帐号密码错误" 61 | else: self.message = message 62 | Logging.error(self.message) 63 | 64 | 65 | class NetworkError(Exception): 66 | def __init__(self, message): 67 | if type(message) != type("") or message == "": self.message = u"网络异常" 68 | else: self.message = message 69 | Logging.error(self.message) 70 | 71 | 72 | class AccountError(Exception): 73 | def __init__(self, message): 74 | if type(message) != type("") or message == "": self.message = u"帐号类型错误" 75 | else: self.message = message 76 | Logging.error(self.message) 77 | 78 | 79 | headers = { 80 | 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36", 81 | 'Host': "www.zhihu.com", 82 | 'Origin': "http://www.zhihu.com", 83 | 'Pragma': "no-cache", 84 | 'Referer': "http://www.zhihu.com/", 85 | 'X-Requested-With': "XMLHttpRequest" 86 | } 87 | 88 | 89 | def download_captcha(): 90 | url = "https://www.zhihu.com/captcha.gif" 91 | r = session.get(url, params={"r": int(time.time()), "type": "login"}, verify=False, headers=headers) 92 | if int(r.status_code) != 200: 93 | raise NetworkError(u"验证码请求失败") 94 | image_name = u"verify." + r.headers['content-type'].split("/")[1] 95 | open( image_name, "wb").write(r.content) 96 | """ 97 | System platform: https://docs.python.org/2/library/platform.html 98 | """ 99 | Logging.info(u"正在调用外部程序渲染验证码 ... ") 100 | if platform.system() == "Linux": 101 | Logging.info(u"Command: xdg-open %s &" % image_name ) 102 | os.system("xdg-open %s &" % image_name ) 103 | elif platform.system() == "Darwin": 104 | Logging.info(u"Command: open %s &" % image_name ) 105 | os.system("open %s &" % image_name ) 106 | elif platform.system() in ("SunOS", "FreeBSD", "Unix", "OpenBSD", "NetBSD"): 107 | os.system("open %s &" % image_name ) 108 | elif platform.system() == "Windows": 109 | os.system("%s" % image_name ) 110 | else: 111 | Logging.info(u"我们无法探测你的作业系统,请自行打开验证码 %s 文件,并输入验证码。" % os.path.join(os.getcwd(), image_name) ) 112 | 113 | sys.stdout.write(termcolor.colored(u"请输入验证码: ", "cyan") ) 114 | captcha_code = raw_input( ) 115 | return captcha_code 116 | 117 | 118 | def search_xsrf(): 119 | url = "http://www.zhihu.com/" 120 | r = session.get(url, verify=False, headers=headers) 121 | if int(r.status_code) != 200: 122 | raise NetworkError(u"验证码请求失败") 123 | results = re.compile(r"\(.*)', index.content)[0] 188 | return True, username 189 | else: 190 | print termcolor.colored("正在为您登录...", "magenta") 191 | return False, None 192 | 193 | 194 | def read_account_from_config_file(config_file="config.ini"): 195 | # NOTE: The ConfigParser module has been renamed to configparser in Python 3. 196 | # The 2to3 tool will automatically adapt imports when converting your sources to Python 3. 197 | # https://docs.python.org/2/library/configparser.html 198 | from ConfigParser import ConfigParser 199 | cf = ConfigParser() 200 | if os.path.exists(config_file) and os.path.isfile(config_file): 201 | Logging.info(u"正在加载配置文件 ...") 202 | cf.read(config_file) 203 | 204 | email = cf.get("account", "mail") 205 | password = cf.get("account", "password") 206 | if email == "" or password == "": 207 | Logging.warn(u"帐号信息无效") 208 | return (None, None) 209 | else: return (email, password) 210 | else: 211 | Logging.error(u"配置文件加载失败!") 212 | return (None, None) 213 | 214 | 215 | def login(account=None, password=None, debug=False): 216 | if islogin() == True: 217 | Logging.success(u"你已经登录过咯") 218 | return True 219 | 220 | if account == None: 221 | (account, password) = read_account_from_config_file() 222 | if account == None: 223 | sys.stdout.write(u"请输入登录账号: ") 224 | account = raw_input() 225 | password = getpass("请输入登录密码: ") 226 | 227 | form_data = build_form(account, password) 228 | """ 229 | result: 230 | {"result": True} 231 | {"error": {"code": 19855555, "message": "unknown.", "data": "data" } } 232 | {"error": {"code": -1, "message": u"unknown error"} } 233 | """ 234 | result = upload_form(form_data) 235 | if "error" in result: 236 | if result["error"]['code'] == 1991829: 237 | # 验证码错误 238 | Logging.error(u"验证码输入错误,请准备重新输入。" ) 239 | return login(debug=True) 240 | elif result["error"]['code'] == 100005: 241 | # 密码错误 242 | Logging.error(u"密码输入错误,请准备重新输入。" ) 243 | return login() 244 | else: 245 | Logging.warn(u"unknown error." ) 246 | return False 247 | elif "result" in result and result['result'] == True: 248 | # 登录成功 249 | Logging.success(u"登录成功!" ) 250 | if debug: 251 | print termcolor.colored("执行python zhihu.py, 可以体验在终端中刷知乎咯.^_^", "cyan") 252 | session.cookies.save() 253 | return True 254 | 255 | if __name__ == "__main__": 256 | # login(account="xxxx@email.com", password="xxxxx") 257 | flag, username = islogin(True) 258 | if not flag: 259 | login(debug=True) 260 | #login() 261 | 262 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.4.1 2 | bs4==0.0.1 3 | requests==2.10.0 4 | termcolor==1.1.0 5 | wheel==0.26.0 6 | -------------------------------------------------------------------------------- /zhihu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: 1.0 6 | @author: lizheming 7 | @contact: nkdudu@126.com 8 | @site: lizheming.top 9 | @file: zhihu.py 10 | """ 11 | 12 | from login import islogin, login 13 | #from logo import logo 14 | import requests 15 | import cookielib 16 | from bs4 import BeautifulSoup 17 | import re 18 | import os 19 | import json 20 | import termcolor 21 | import threading 22 | import time 23 | import random 24 | import sys 25 | reload(sys) 26 | sys.setdefaultencoding('utf-8') 27 | 28 | 29 | logo = ''\ 30 | ' $$'\ 31 | ' $$$ &&&&$$$$ ##$$$$$$$$$$$$$$$$$$#$$$ \n'\ 32 | ' $$$ $$$$$$$$$$$$$$$ ##$$$$$$$$$$$$$$$$$$o; ;\n'\ 33 | ' $$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$ *$$o #\n'\ 34 | ' $$$ $$$ $$$ $$$ $$$ *$$o $$$$\n'\ 35 | '$$* $$$ $$$ $$$ $$$$ *$$o $$$$\n'\ 36 | ' $$$ $$$ $$$ $$$$ *$$o $$$$\n'\ 37 | ' $$o $$$ $$$ $$$ *$$o $$$o\n'\ 38 | ';$$$$$$$$$$$$$$$$ $$$ $$$ *$$o\n'\ 39 | '$$$$$$$$$$$$$$$$$* $$$ $$$ ;$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n'\ 40 | ' $$$ $$$ $$$ *$$o\n'\ 41 | ' $$$ $$$ $$$ *$$o\n'\ 42 | ' $$$$$$$ $$$ $$$ *$$o\n'\ 43 | ' $$$; $$$$ $$$ $$$ *$$o\n'\ 44 | ' $$$$ $$$ $$$$$ $$$$$$$$$ *$$o\n'\ 45 | ' $$$$! $$ $$$$* $$$;\n'\ 46 | '$$$$$ ; $$$$$$$$$$$\n'\ 47 | '$$$$$$\n' 48 | 49 | 50 | 51 | zhihu = "https://www.zhihu.com" 52 | session = requests.Session() 53 | session.cookies = cookielib.LWPCookieJar("cookies") 54 | datas = [] 55 | tlitems = [] 56 | flag = True 57 | op_stop = False 58 | offset = 0 59 | temp = 0 60 | limit = 5 61 | tid = None 62 | _xsrf = None 63 | 64 | headers = { 65 | 'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", 66 | 'Host': "www.zhihu.com", 67 | #"Referer": "www.zhihu.com" 68 | } 69 | 70 | 71 | def mul_get_request(session, url, headers, timeout=10, n=5, **kwargs): 72 | t = 0 73 | while n: 74 | if t == 2: 75 | print termcolor.colored("网络缓慢,请稍后..", "red") 76 | try: 77 | res = session.get(url, headers=headers, timeout=timeout, **kwargs) 78 | return res 79 | except: 80 | n -= 1 81 | t += 1 82 | exit() 83 | return None 84 | 85 | 86 | def mul_post_request(session, url, headers, timeout=10, n=5, **kwargs): 87 | t = 0 88 | while n: 89 | if t == 2: 90 | print termcolor.colored("网络缓慢,请稍后..", "red") 91 | try: 92 | res = session.post(url, headers=headers, timeout=timeout, **kwargs) 93 | return res 94 | except: 95 | n -= 1 96 | t += 1 97 | exit() 98 | return None 99 | 100 | 101 | def loadsession(): 102 | global session 103 | try: 104 | session.cookies.load(ignore_discard="true") 105 | except: 106 | termcolor.colored("加载异常", "red") 107 | pass 108 | 109 | 110 | loadsession() 111 | l, username = islogin() 112 | if not l: 113 | if not login(): 114 | sys.exit() 115 | loadsession() 116 | 117 | 118 | def index(): 119 | global tid 120 | global _xsrf 121 | global session 122 | 123 | #res = session.get(zhihu, headers=headers) 124 | res = mul_get_request(session=session, url=zhihu, headers=headers) 125 | if not res: 126 | sys.exit() 127 | #print res.content 128 | _xsrf = re.findall(r'name="_xsrf" value="(\S+)"', res.content)[0] 129 | 130 | print res.content 131 | 132 | soup = BeautifulSoup(res.content, "html.parser") 133 | items = soup.select(".feed-item.folding.feed-item-hook") 134 | for item in items: 135 | #tid, t, l = get_item_info_another(item) 136 | from TimeLine import TLItem 137 | iitem = TLItem(item, _xsrf) 138 | tid, t, l = iitem.get_item_info() 139 | datas.append([t, l, tid]) 140 | tlitems.append(iitem) 141 | 142 | 143 | def worker(): 144 | global tid 145 | global datas 146 | global offset 147 | global session 148 | 149 | url = "https://www.zhihu.com/node/HomeFeedListV2" 150 | params = { 151 | "start": tid, 152 | "offset": 21 153 | } 154 | data = { 155 | "method":"next", 156 | "_xsrf":_xsrf, 157 | "params":json.dumps(params) 158 | } 159 | while flag: 160 | if len(datas) - offset > 10 * limit: 161 | time.sleep(6) 162 | continue 163 | try: 164 | res = session.post(url, data, headers=headers) 165 | except: 166 | continue 167 | msgs = None 168 | try: 169 | msgs = res.json()["msg"] 170 | except: 171 | # print res.content 172 | # print "link error 1326" 173 | continue 174 | for msg in msgs: 175 | soup = BeautifulSoup(msg, "html.parser") 176 | item = soup.select(".feed-item.folding.feed-item-hook")[0] 177 | from TimeLine import TLItem 178 | iitem = TLItem(item, _xsrf) 179 | tid, t, l = iitem.get_item_info() 180 | datas.append([t, l, tid]) 181 | tlitems.append(iitem) 182 | params["start"] = tid 183 | params["offset"] += 21 184 | data["params"] = json.dumps(params) 185 | time.sleep(6) 186 | 187 | 188 | def welcome(): 189 | clear() 190 | print termcolor.colored(logo, "cyan") 191 | print termcolor.colored("Hello {}, 欢迎使用终端版知乎".format(username), "yellow") 192 | 193 | 194 | def next_page(**kwargs): 195 | clear() 196 | global op_stop 197 | op_stop = True 198 | 199 | 200 | def pre_page(**kwargs): 201 | clear() 202 | global offset 203 | global op_stop 204 | op_stop = True 205 | offset = max(0, offset - limit*2) 206 | 207 | 208 | def pwd(): 209 | global temp 210 | global offset 211 | 212 | clear() 213 | offset -= limit 214 | temp = offset 215 | for x in range(limit): 216 | data = datas[temp + x] 217 | print offset 218 | print "\n".join(i for i in data[0] if i) + "\n" 219 | offset += 1 220 | 221 | 222 | def bye(): 223 | global flag 224 | global op_stop 225 | flag = False 226 | op_stop = True 227 | print termcolor.colored("Bye", "cyan") 228 | print termcolor.colored("有任何建议欢迎与我联系: nkdudu@126.com", "cyan") 229 | 230 | 231 | def clear(): 232 | i = os.system("clear") 233 | 234 | 235 | def help(): 236 | info = "\n" \ 237 | "**********************************************************\n" \ 238 | "**\n" \ 239 | "** 回车: 下一页\n" \ 240 | "** next: 下一页\n" \ 241 | "** pre: 上一页\n" \ 242 | "** pwd: 当前页\n" \ 243 | "** #Num.: 选中具体TL条目进行操作(只限当前页中的条目)\n" \ 244 | "** clear: 清屏\n" \ 245 | "** quit: 退出系统\n" \ 246 | "**\n" \ 247 | "**********************************************************\n" 248 | print termcolor.colored(info, "green") 249 | 250 | 251 | def error(): 252 | print termcolor.colored("输入错误, 可通过", "red") + termcolor.colored("help", "cyan") + termcolor.colored("查看", "red") 253 | 254 | 255 | def exit(): 256 | global flag 257 | global op_stop 258 | flag = False 259 | op_stop = True 260 | print termcolor.colored("因网络故障程序退出,请检查您的网络设置", "yellow") 261 | 262 | 263 | main_ops = { 264 | "": next_page, 265 | "next": next_page, 266 | "pre": pre_page, 267 | "pwd": pwd, 268 | "clear": clear, 269 | "quit": bye, 270 | "exit": bye, 271 | "help": help 272 | } 273 | 274 | 275 | def main(): 276 | global flag 277 | global offset 278 | global temp 279 | global op_stop 280 | global thread 281 | 282 | ithread = threading.Thread(target=index) 283 | ithread.start() 284 | welcome() 285 | ithread.join() 286 | 287 | thread = threading.Thread(target=worker) 288 | thread.start() 289 | mode = re.compile(r"^\d+$") 290 | while flag: 291 | temp = offset 292 | x = 0 293 | while x < limit: 294 | if (temp + x) >= len(datas): 295 | termcolor.colored("访问速度过快,请稍候", "magenta") 296 | continue 297 | data = datas[temp + x] 298 | print offset 299 | print "\n".join(i for i in data[0] if i) + "\n" 300 | offset += 1 301 | x += 1 302 | x = 0 303 | 304 | op_stop = False 305 | while not op_stop: 306 | op = raw_input("Time Line$ ") 307 | if not re.match(mode, op.strip()): 308 | main_ops.get(op, error)() 309 | else: 310 | opn = int(op) 311 | if temp <= opn < offset: 312 | item = tlitems[opn] 313 | if item.operate(): 314 | bye() 315 | flag = False 316 | break 317 | else: 318 | print termcolor.colored("请输入正确的序号", "red") 319 | thread.join() 320 | 321 | 322 | if __name__ == "__main__": 323 | main() 324 | --------------------------------------------------------------------------------