[转载]给中国学生的第四封信:大学四年应是这样度过 by 李开复--www.kaifulee.com

本文针对大学生成长问题,指出大学是人生关键阶段。建议大学生掌握七项学习,包括自修之道,培养举一反三、无师自通能力;学好数学、英语等基础知识;积极主动规划未来;合理掌控时间;学会为人处事等,以收获自信和美好未来。

     今天,我回复了“开复学生网”开通以来的第1000个问题。关掉电脑后,始终有一封学生来信萦绕在我的脑海里,挥之不去:

      开复老师:
         就要毕业了。
         回头看自己所谓的大学生活,
         我想哭,不是因为离别,而是因为什么都没学到。
         我不知,简历该怎么写,若是以往我会让它空白。
         最大的收获也许是……对什么都没有的忍耐和适应……



     这封来信道出了不少大三、大四学生的心声。大学期间,有许多学生放任自己、虚度光阴,还有许多学生始终也找不到正确的学习方向。当他们被第一次补考通知唤醒时,当他们收到第一封来自应聘企业的婉拒信时,这些学生才惊讶地发现,自己的前途是那么渺茫,一切努力似乎都为时已晚……
这“第四封信”是写给那些希望早些从懵懂中清醒过来的大学生,那些从未贪睡并希望把握自己的前途和命运的大学生以及那些即将迈进大学门槛的未来大学生们的。在这封信中,我想对所有同学说: 

     大学是人一生中最为关键的阶段。从入学的第一天起,你就应当对大学四年有一个正确的认识和规划。为了在学习中享受到最大的快乐,为了在毕业时找到自己最喜爱的工作,每一个刚进入大学校园的人都应当掌握七项学习:学习自修之道、基础知识、实践贯通、兴趣培养、积极主动、掌控时间、为人处事。只要做好了这七点,大学生临到毕业时的最大收获就绝不会是“对什么都没有的忍耐和适应”,而应当是“对什么都可以有的自信和渴望”。只要做好了这七点,你就能成为一个有潜力、有思想、有价值、有前途的快乐的毕业生。
 

大学:人生的关键

   大学是人生的关键阶段。这是因为,进入大学是你终于放下高考的重担,第一次开始追逐自己的理想、兴趣。这是你离开家庭生活,第一次独立参与团体和社会生活。这是你不再单纯地学习或背诵书本上的理论知识,第一次有机会在学习理论的同时亲身实践。这是你第一次不再由父母安排生活和学习中的一切,而是有足够的自由处置生活和学习中遇到的各类问题,支配所有属于自己的时间。

   大学是人生的关键阶段。这是因为,这是你一生中最后一次有机会系统性地接受教育。这是你最后一次能够全心建立你的知识基础。这可能是你最后一次可以将大段时间用于学习的人生阶段,也可能是最后一次可以拥有较高的可塑性、集中精力充实自我的成长历程。这也许是你最后一次能在相对宽容的,可以置身其中学习为人处世之道的理想环境。

  
大学是人生的关键阶段。在这个阶段里,所有大学生都应当认真把握每一个“第一次”,让它们成为未来人生道路的基石;在这个阶段里,所有大学生也要珍惜每一个“最后一次”,不要让自己在不远的将来追悔莫及。在大学四年里,大家应该努力为自己编织生活梦想,明确奋斗方向,奠定事业基础。

  
大学四年每个人都只有一次,大学四年应这样度过……

 

自修之道:从举一反三到无师自通

   记得我在哥伦比亚大学任助教时,曾有位中国学生的家长向我抱怨说:“你们大学里到底在教些什么?我孩子读完了大二计算机系,居然连VisiCalc[1] 都不会用。”

   我当时回答道:“电脑的发展日新月异。我们不能保证大学里所教的任何一项技术在五年以后仍然管用,我们也不能保证学生可以学会每一种技术和工具。我们能保证的是,你的孩子将学会思考,并掌握学习的方法,这样,无论五年以后出现什么样的新技术或新工具,你的孩子都能游刃有余。”

   她接着问:“学最新的软件不是教育,那教育的本质究竟是什么呢?”

   我回答说:“如果我们将学过的东西忘得一干二净时,最后剩下来的东西就是教育的本质了。

   我当时说的这句话来自教育家B. F. Skinner的名言。所谓“剩下来的东西”,其实就是自学的能力,也就是举一反三或无师自通的能力。大学不是“职业培训班”,而是一个让学生适应社会,适应不同工作岗位的平台。在大学期间,学习专业知识固然重要,但更重要的还是要学习独立思考的方法,培养举一反三的能力,只有这样,大学毕业生才能适应瞬息万变的未来世界。我认识的不少在中国读完大学来美国念研究生的朋友。他们认为来美国后,不论是学习,工作还是生活他们最缺乏的是独立思考的能力因为在国内时他们很少独立思考和独立决策。

   上中学时,老师会一次又一次重复每一课里的关键内容。但进了大学以后,老师只会充当引路人的角色,学生必须自主地学习、探索和实践。走上工作岗位后,自学能力就显得更为重要了。微软公司曾做过一个统计:在每一名微软员工所掌握的知识内容里,只有大约10%是员工在过去的学习和工作中积累得到的,其他知识都是在加入微软后重新学习的。这一数据充分表明,一个缺乏自学能力的人是难以在微软这样的现代企业中立足的。

   自学能力必须在大学期间开始培养。许多同学总是抱怨老师教得不好,懂得不多,学校的课程安排也不合理。我通常会劝这些学生说:“与其诅咒黑暗,不如点亮蜡烛”。 大学生不应该只会跟在老师的身后亦步亦趋,而应当主动走在老师的前面。例如,大学老师在一个课时里通常要涵盖课本中几十页的信息内容,仅仅通过课堂听讲是无法把所有知识学通、学透的。最好的学习方法是在老师讲课之前就把课本中的相关问题琢磨清楚,然后在课堂上对照老师的讲解弥补自己在理解和认识上的不足之处。

   中学生在学习知识时更多地是追求“记住”知识,而大学生就应当要求自己“理解”知识并善于提出问题。对每一个知识点,都应当多问几个“为什么”。一旦真正理解了理论或方法的来龙去脉,大家就能举一反三地学习其他知识,解决其他问题,甚至达到无师自通的境界。

   事实上,很多问题都有不同的思路或观察角度。在学习知识或解决问题时,不要总是死守一种思维模式,不要让自己成为课本或经验的奴隶。只有在学习中敢于创新,善于从全新的角度出发思考问题,学生潜在的思考能力、创造能力和学习能力才能被真正激发出来。

   《礼记·学记》上讲:“独学而无友,则孤陋而寡闻”。也就是说,大学生应当充分利用学校里的人才资源,从各种渠道吸收知识和方法。如果遇到好的老师,你可以主动向他们请教,或者请他们推荐一些课外的参考读物。除了资深的教授以外,大学中的青年教师、博士生、硕士生乃至自己的同班同学都是最好的知识来源和学习伙伴。每个人对问题的理解和认识都不尽相同,只有互帮互学,大家才能共同进步。

   有些同学曾告诉我说,他们很羡慕我在读书时能有一位获得过图灵奖的大师传道授业。其实,虽然我非常推崇我的老师,但他在大学期间并没有教给我多少专业知识。他只是给我指明了大方向,让我分享他的经验,给我提供研究的资源,并教我做人的方法。他没有时间也没有必要指导我学习具体的专业知识。我在大学期间积累的专业知识都是通过自学获得的。刚入门时,我曾多次红着脸向我的师兄请教最基本的知识内容,开会讨论时我曾问过不少肤浅的问题,课余时间我还主动与同学探讨、切磋。“三人行必有我师”,大学生的周围到处是良师益友。只要珍惜这些难得的机会,大胆发问,经常切磋,我们就能学到最有用的知识和方法。

   大学生应该充分利用图书馆和互联网,培养独立学习和研究的本领,为适应今后的工作或进一步的深造做准备。首先,除了学习老师规定的课程以外,大学生一定要学会查找书籍和文献,以便接触更广泛的知识和研究成果。例如,当我们在一门课上发现了自己感兴趣的课题,就应当积极去图书馆查阅相关文献,了解这个课题的来龙去脉和目前的研究动态。熟练和充分地使用图书馆资源,这是大学生特别是那些有志于科学研究的大学生的必备技能之一。读书时,应尽量多读一些英文原版教材。有些原版教材写得深入浅出,附有大量实例,比中文教材还适于自学。其次,在书本之外,互联网也是一个巨大的资源库,大学生们可以借助搜索引擎在网上查找各类信息。“开复学生网”开通半年以来,我发现很多同学其实并没有很好地掌握互联网的搜索技巧,有时他们提出的问题只要在搜索引擎中简单检索一下,就能轻易找到答案。还有些同学很容易相信网上的谣言,而不会利用搜索引擎自己查考、求证。除了搜索引擎以外,网上还有许多网站和社区也是很好的学习园地。

   自学时,不要因为达到了学校的要求就沾沾自喜,也不要认为自己在大学里功课好就足够了。在二十一世纪的今天,人才已经变成了一个国际化的概念。当你对自己的成绩感到满意时,我建议你开始自学一些国际一流大学的课程。例如,美国麻省理工学院(MIT)的开放式课程已经在网上无偿发布出来,大家不妨去看看MIT的网上课程,做做MIT的网上试题。当你可以自如地掌握MIT课程时,你就可以更加自信地面对国际化的挑战了。

   总之,善于举一反三,学会无师自通,这是大学四年中你可以送给自己的最好的礼物。

 

基础知识:数学、英语、计算机、互联网

   我曾经说过,中国学生的一大优势是扎实的基础知识,如数学、物理等。但是,最近几年,同学们在目睹了很多速成的例子(如丁磊、陈天桥等)之后,也迫切希望能驶上成功的快车道。这渐渐形成了一种追求速成的浮躁风气。有许多大学生梦想在毕业后就立即能做“经理”、“老板”,还有许多大学生入学时直接选择了“管理”专业,因为他们认为从这样的专业毕业后马上就可以成为企业的管理者。可不少学生进入了管理专业后,才发现自己对本专业的学习毫无兴趣。其实,管理专业和其他专业一样,都是传授基础知识和基本方法的地方,没有哪个专业可以保证学生在毕业时就能走上领导岗位。无论同学们所学的是哪个专业,大学毕业才是个人事业的真正开始。想做企业领导或想做管理工作的同学也必须从基层做起,必须首先在人品方面学会做人,在学业方面打好基础。

   如果说大学是一个学习和进步的平台,那么,这个平台的地基就是大学里的基础课程。在大学期间,同学们一定要学好基础知识其中包括数学、英语、计算机和互联网的使用,以及本专业要求的基础课程(如商学院的财务、经济等课程)。在科技发展日新月异的今天,应用领域里很多看似高深的技术在几年后就会被新的技术或工具取代。只有对基础知识的学习才可以受用终身。另一方面,如果没有打下好的基础,大学生们也很难真正理解高深的应用技术。最后,在许多的中国大学里,教授对基础课程也比对最新技术有更丰富的教学经验。

  
数学是理工科学生必备的基础。很多学生在高中时认为数学是最难学的,到了大学里,一旦发现本专业对数学的要求不高,就会彻底放松对数学知识的学习,而且他们看不出数学知识有什么现实的应用或就业前景。但大家不要忘记,绝大多数理工科专业的知识体系都建立在数学的基石之上。例如,要想学好计算机工程专业,那至少要把离散数学(包括集合论、图论、数理逻辑等)、线性代数、概率统计和数学分析学好;要想进一步攻读计算机科学专业的硕士或博士学位,可能还需要更高的数学素养。同时,数学也是人类几千年积累的智慧结晶,学习数学知识可以培养和训练人的思维能力。通过对几何的学习,我们可以学会用演绎、推理来求证和思考的方法;通过学习概率统计,我们可以知道该如何避免钻进思维的死胡同,该如何让自己面前的机会最大化。所以,大家一定要用心把数学学好,不能敷衍了事。学习数学也不能仅仅局限于选修多门数学课程,而是要知道自己为什么学习数学,要从学习数学的过程中掌握认知和思考的方法。

   二十一世纪里最重要的沟通工具就是英语。有些同学在大学里只为了考过四级、六级而学习英语,有的同学仅仅把英语当作一种求职必备的技能来学习,甚至还有人认为学习和使用英语等于崇洋媚外。其实,学习英语的根本目的是为了掌握一种重要的学习和沟通工具。在未来的几十年里,世界上最全面的新闻内容,最先进的思想和最高深的技术,以及大多数知识分子间的交流都将用英语进行。因此,除非你甘心做一个与国际脱节的人,英语学习是至关重要的。在软件行业里,不但编程语言是以英语为基础设计出来的,最重要的教材、论文、参考资料、用户手册等资源也大多是用英语写就的。学英语绝不等于崇洋媚外。中国正在走向世界,中国需要学习西方的先进思想和先进科学技术,学好英语才是真正的爱国。

   很多中国留学生的英语考试成绩不错,也高分考过四级、六级、托福,但是留学美国后上课时却很难听懂课程内容,和外国同学交流时就更加困难。我们该如何学好英语呢?既然英语是最重要的沟通工具,那么,最重要的学习方法就是尽量与实践结合起来,不能只“学”不“用”,更不能只靠背诵的方式学习英语。读书时,大家尽量阅读原版的专业教材(如果英语不够好,可以先从中英对照的教材看起),并适当地阅读一些自己感兴趣的专业论文,这可以同时提高英语和相关专业的知识水平。其次,提高英语听说能力的最好方法是直接与那些以英语为母语的外国人对话。现在有很多在中国学习和工作的外国人,他们中的不少人为了学中文,很愿意与中国学生对话、交流,这是很好的学习机会。此外,大家不要把学英语当作一件苦差事,完全可以用有趣的方法学习英语。例如,可以多看一些名人的对话或演讲,多看一些小说、戏剧甚至漫画。初学者可以找英文原版的教学节目和录像来学习,有一定基础的则应该看英文电视或电影。看一部英文电影时,最好先在有字幕的时候看一遍,同时查考生词、熟悉句式,然后在不加字幕的情况下再看一遍,仅靠耳朵去听。听英文广播也是很好的练习英文听力的方法,大家每天最好能抽出半小时到一小时的时间收听广播并尽量理解其中的内容,有必要的话还可以录下来反复收听。在互联网上也有许多互动式的英语学习网站,大家可以在网站上用游戏、自我测试、双语阅读等方式提升英语水平。总之,勇于实践、持之以恒是学习英语的必由之路。

   信息时代已经到来,大学生在信息科学与信息技术方面的素养也已成为他们进入社会的必备基础之一。虽然不是每个大学生都需要懂得计算机原理和编程知识,但所有大学生都应能熟练地使用计算机、互联网、办公软件和搜索引擎,都应能熟练地在网上浏览信息和查找专业知识。在二十一世纪里,使用计算机和网络就像使用纸和笔一样是人人必备的基本功。不学好计算机,你就无法快捷全面地获得自己需要的知识或信息。

  
最后,每个特定的专业也有它自己的基础课程。以计算机专业为例,许多大学生只热衷于学习最新的语言、技术、平台、标准和工具,因为很多公司在招聘时都会要求这些方面的基础或经验。这些新技术虽然应该学习,但计算机基础课程的学习更为重要,因为语言和平台的发展日新月异,但只要学好基础课程(如数据结构、算法、编译原理、计算机原理、数据库原理等)就可以万变不离其宗。有位同学生动地把这些基础课程比拟为计算机专业的内功,而把新的语言、技术、平台、标准和工具比拟为外功。那些只懂得追求时髦的学生最终只知道些招式的皮毛,而没有内功的积累,他们是不可能成为真正的高手的。

   虽然我一向鼓励大家追寻自己的兴趣,但在这里仍需强调,生活中有些事情即便不感兴趣也是必须要做的。例如,打好基础,学好数学、英语和计算机的使用就是这一类必须做的事情。如果你对数学、英语和计算机有兴趣,那你是幸运儿,可以享受学习的乐趣;但就算你没有兴趣,你也必须把这些基础打好。打基础是苦功夫,不愿吃苦是不能修得正果的。

 

实践贯通:“做过的才真正明白”

   上高中时,许多学生会向老师提出“为什么?有什么用?”的问题,通常,老师给出的答案都是“不准问”。进入大学后,这些问题的答案应该是“不准不问”。在大学里,同学们应该懂得每一个学科的知识、理论、方法与具体的实践、应用如何结合起来,尤其是工科的学生更是如此。

  
有一句关于实践的谚语是这样说的:“我听到的会忘掉,我看到的能记住,我做过的才真正明白。

  
无论学习何种专业、何种课程,如果能在学习中努力实践,做到融会贯通,我们就可以更深入地理解知识体系,可以牢牢地记住学过的知识。因此,我建议同学们多选些与实践相关的专业课。实践时,最好是几个同学合作,这样,既可经过实践理解专业知识,也可以学会如何与人合作,培养团队精神。如果有机会在老师手下做些实际的项目,或者走出校门打工,只要不影响课业,这些做法都是值得鼓励的。外出打工或做项目时,不要只看重薪酬待遇(除非生活上确实有困难),有时候,即便待遇不满意,但有许多培训和实践的机会,我们也值得一试。

  
以计算机专业为例,实践经验对于软件开发来说更是必不可少的。微软公司希望应聘程序员的大学毕业生最好有十万行的编程经验。
理由很简单:实践性的技术要在实践中提高。计算机归根结底是一门实践的学问,不动手是永远也学不会的。因此,最重要的不是在笔试中考高分,而是实践能力。但是,在与中国学生的交流过程中,我很惊讶地发现,中国某些学校计算机系的学生到了大三还不会编程。这些大学里的教学方法和课程的确需要更新。如果你不巧是在这样的学校中就读,那你就应该从打工、自学或上网的过程中寻求学习和实践的机会。在网上可以找到许多实践项目,例如,有一批爱好编程的学生建立了一个讨论软件技术的网站(www.diyinside.com),在其中共享他们的知识和实践经验,并成功举办了很多次活动(如在各大高校举办校园技术教育会议),还出版了帮助学生提高技术、解答疑难方面的图书,该网站有多位成员获得了“微软最有价值的专家”的称号。


 

培养兴趣:开拓视野,立定志向

   孔子说:“知之者不如好之者,好之者不如乐之者。”我在“给中国学生的第三封信”中曾深入论述了快乐和兴趣是一个人成功的关键。如果你对某个领域充满激情,你就有可能在该领域中发挥自己所有的潜力,甚至为它而废寝忘食。这时候,你已经不是为了成功而学习,而是为了“享受”而学习了。在“第三封信”中,我也曾谈到我自己是如何在大学期间放弃了我不感兴趣的法律专业而进入我所热爱的计算机专业学习的。

  
有些同学问我,如何像我一样能找到自己的兴趣呢?我觉得,首先要客观地评估和寻找自己的兴趣所在:不要把社会、家人或朋友认可和看重的事当作自己的爱好;不要以为有趣的事就是自己的兴趣所在,而是要亲身体验它并用自己的头脑做出判断;不要以为有兴趣的事情就可以成为自己的职业,例如,喜欢玩网络游戏并不代表你会喜欢或有能力开发网络游戏;不要以为有兴趣就意味着自己有这方面的天赋,不过,你可以尽量寻找天赋和兴趣的最佳结合点,例如,如果你对数学有天赋但又喜欢计算机专业,那么你完全可以做计算机理论方面的研究工作。

   最好的寻找兴趣点的方法是开拓自己的视野,接触众多的领域。唯有接触你才能尝试,唯有尝试你才能找到自己的最爱。而大学正是这样一个可以让你接触并尝试众多领域的独一无二的场所。因此,大学生应当更好地把握在校时间,充分利用学校的资源,通过使用图书馆资源、旁听课程、搜索网络、听讲座、打工、参加社团活动、与朋友交流、使用电子邮件和电子论坛等不同方式接触更多的领域、更多的工作类型和更多的专家学者。当年,如果我只是乖乖地到法律系上课,而不去尝试旁听计算机系的课程,我就不会去计算机中心打工,也不去找计算机系的助教切磋,就更不会发现自己对计算机的浓厚兴趣。

  
通过开拓视野和接触尝试,如果你发现了自己真正的兴趣爱好,这时就可以去尝试转系的可能性、尝试课外学习、选修或旁听相关课程;你也可以去找一些打工或假期实习的机会,进一步理解相关行业的工作性质;或者,努力去考自己感兴趣专业的研究生,重新进行一次专业选择。其实,本科读什么专业并不能完全决定毕业后的工作方向,正如我所强调的那样,大学期间的学习过程培养的是你的学习能力,只要具备了这种能力,即使从事的是全新的工作,你也能在边做边学的过程中获取足够的知识和经验。

   除了“选你所爱”,大家也不妨试试“爱你所选”。有些同学后悔自己在入学时选错了专业,以至于对所学的专业缺乏兴趣,没有学习动力;有些同学则因为追寻兴趣而“走火入魔”,毕业后才发现荒废了本专业的课程;另一些同学因为在学习上遇到了困难或对本专业抱有偏见,就以兴趣为借口,不愿意面对自己的专业。这些做法都是不正确的。在大学中,转系可能并不容易,所以,大家首先应尽力试着把本专业读好,并在学习过程中逐渐培养自己对本专业的兴趣。此外,一个专业里可能有很多不同的领域,也许你对专业里的某一个领域会有兴趣。现在,有很多专业发展了交叉学科,两个专业的结合往往是新的增长点。因此,只要多接触、多尝试,你也许就会碰到自己真正感兴趣的方向。“数字笔”的发明人王坚博士在微软亚洲研究院负责用户界面的研究,可是谁又能想到他从本科到博士所学的都是心理学专业,而用户界面又正是计算机和心理学专业的最佳结合点。另一方面,就算你毕业后要从事其他的行业,你依然可以把自己的专业读好,这同样能成为你在新行业中的优势。例如,有一位同学不喜欢读工科,想毕业后进入服务业发展,我就建议他先把工科读好,将来可以在服务业中以精通技术作为自己的特长。

  
人生的路很长,每个人都可以有很多不同的兴趣爱好。在追寻兴趣之外,更重要的是要找寻自己终身不变的志向。有一本书的作者曾访问了几百个成功者,问他们有哪件事是他们今天已经懂得,但在年轻时却留下了遗憾的事情。在受访者的回答中,最多的一种是:“希望在年轻时就有前辈告诉我、鼓励我去追寻自己的理想和志向。”相比之下,兴趣固然关键,但志向更为重要。例如,我的志向是“使影响力最大化”,多年以来,我有许多兴趣爱好,如语音识别、对弈软件、多媒体、研究到开发的转换、管理学、满足用户的需求、演讲和写作、帮助中国学生等等,兴趣可以改变,但我的志向是始终不渝的。因此,大家不必把某种兴趣当作自己最后的目标,也不必把任何一种兴趣的发展道路完全切断,在志向的指引下,不同的兴趣完全可以平行发展,实在必要时再做出最佳的抉择。志向就像罗盘,兴趣就像风帆,两者相辅相成、缺一不可,它们可以让你驶向理想的港湾。

 

积极主动:果断负责,创造机遇

   创立“开复学生网”时,我的初衷是“帮助学生帮助自己”。但让我很惊讶的是,更多的学生希望我直接帮他们做出决定,甚至仅在简短的几句自我介绍后就直接对我说:“只有你能告诉我,我该怎么做”。难道一个陌生人会比你更知道自己该怎么做吗?我慢慢认识到,这种被动的思维方式是从小在中国的教育环境中培养出来的。被动的人总是习惯性地认为他们现在的境况是他人和环境造成的,如果别人不指点,环境不改变,自己就只有消极地生活下去。持有这种态度的人,事业还没有开始,自己就已经被击败,我从来没见过这样消极的人可以取得持续的成功。


   从大学的第一天开始,你就必须从被动转向主动,你必须成为自己未来的主人,你必须积极地管理自己的学业和将来的事业,理由很简单:因为没有人比你更在乎你自己的工作与生活。“让大学生活对自己有价值”是你的责任。许多同学到了大四才开始做人生和职业规划,而一个主动的学生应该从进入大学时就开始规划自己的未来。

  
积极主动的第一步是要有积极的态度。大家可以用我在“第三封信”里推荐的方法,积极规划自己的人生目标,追寻兴趣并尝试新的知识和领域。纳粹德国某集中营的一位幸存者维克托·弗兰克尔曾说过:“在任何特定的环境中,人们还有一种最后的自由,就是选择自己的态度。”

   积极主动的第二步是对自己的一切负责,勇敢面对人生。不要把不确定的或困难的事情一味搁置起来。比如说,有些同学认为英语重要,但学校不考试就不学英语;或者,有些同学觉得自己需要参加社团磨练人际关系,但是因为害羞就不积极报名。但是,我们必须认识到,不去解决也是一种解决,不做决定也是一个决定,这样的解决和决定将使你面前的机会丧失殆尽。对于这种消极、胆怯的作风,你终有一天会付出代价的。

  
积极主动的第三步是要做好充分的准备:事事用心事事尽力,不要等机遇上门;要把握住机遇,创造机遇。中国科技大学校长朱清时院士在大三时被分配到青海做铸造工人。但他不像其他同学那样放弃学习,整天打扑克、喝酒。他依然终日钻研数理化和英语。六年后,中国科学院要在青海做一个重要的项目,这时朱校长就脱颖而出,开始了他辉煌的事业。很多人可能说他运气好,被分配到缺乏人才的青海,才有这机会。但是,如果他没有努力学习,也无法抓住这个机遇。所以,做好充分的准备,当机遇来临时,你才能抓住它。

  
积极主动的第四步是“以终为始”,积极地规划大学四年。任何规划都将成为你某个阶段的终点,也将成为你下一个阶段的起点,而你的志向和兴趣将为你提供方向和动力。如果不知道自己的志向和兴趣,你应该马上做一个发掘志向和兴趣的计划;如果不知道毕业后要做什么,你应该马上制定一个尝试新领域的计划;如果不知道自己最欠缺什么,你应该马上写一份简历,找你的老师、朋友打分,或自己审阅,看看哪里需要改进;如果毕业后想出国读博士,你应该想想如何让自己在申请出国前有具体的研究经验和学术论文;如果毕业后想进入某个公司工作,你应该收集该公司的招聘广告,以便和你自己的履历对比,看自己还欠缺哪些经验。只要认真制定、管理、评估和调整自己的人生规划,你就会离你自己的目标越来越近。

 

掌控时间:事分轻重缓急,人应自控自觉

   除了积极主动的态度,大学生还要学会安排自己的时间,管理自己的事务。一位同学是这么描述大学生活的:

“大学和高中相比似乎没有什么太大的区别,每天依旧是学习,每次考试后依旧是担心考试成绩……不同的只是大学里上网的时间和睡觉的时间多了很多,压力也小了很多。”


   这位同学并不明白,“时间多了很多”正是大学与高中之间巨大的差别。时间多了,就需要自己安排时间、计划时间、管理时间

  
安排时间出了做一个时间表外,更重要的是“事分轻重缓急”。在《高效能人士的七个习惯》一书中,作者史蒂芬·柯维提出,“重要事”和“紧急事”的差别是人们浪费时间的最大理由之一。因为人的惯性是先做最紧急的事,但这么做会导致一些重要的事被荒废掉。例如,我认为这篇文章里谈到的各种学习都是“重要的”,但它们不见得都是老师布置的必修课业,采纳我的建议的同学们依然会因为考试、交作业等紧急的事情而荒废了打好基础、学习做人等重要的事情。因此,每天管理时间的一种好方法是,早上确定今天要做的紧急事和重要事,睡前回顾一下,这一天有没有做到两者的平衡。

  
每个人都有许多“紧急事”和“重要事”,想把每件事都做到最好是不切实际的。我建议大家把“必须做的事”和“尽量做的事”分开。必须做的事要做到最好,但尽量做的事尽力而为即可。建议大家用良好的态度和宽广的胸怀接受那些你暂时不能改变的事情,多关注那些你能够改变的事情。此外,还要注意生物钟的运行规律,按时作息,劳逸结合,这样才能在学习时有最好的状态。

   大学四年是最容易迷失方向的时期。大学生必须有自控的能力,让自己交些好朋友,学些好习惯,不要沉迷于对自己无益的习惯(如网络游戏)里。
   大学四年是最容易迷失方向的时期。大学生必须有自控的能力,让自己交些好朋友,学些好习惯,不要沉迷于对自己无益的习惯(如网络游戏)里。一位积极、主动的中国学生在“开复学生网”上劝告其他同学:“不要玩游戏,至少不要玩网络游戏。我所认识的专业水平比较高的大学朋友中没有一个玩网络游戏的。沉迷于网络游戏是对于现实的逃避,是不愿面对自己不足的一面。我认为,要脱离网络游戏,就得珍惜自己宝贵的大学时间,找到自己感兴趣的方向,做一些有意义并能给自己带来满足感的事情。
 

为人处事:培养友情,参与群体

   很多大学生入校时都是第一次离开父母,离开自己生长的环境。进入校园开始集体生活后,如何与同学、朋友以及社团的同事相处就成为了大学生学习内容的一部分。大学是大家最后一次可以在相对宽松的环境中学习、培养、训练如何与人相处的机会。在未来,人们在社会里、在工作中与人相处的能力会变得越来越重要,甚至超过了工作本身。所以,大学生要好好把握机会,培养自己的交流意识和团队精神。

  
“人际交往能力不够强,人际圈子不够广,但又没有什么特长可以引起大家的注意,在社团里也不知道怎么和其他人有效地建立联系。”这是一些大学生在人际交往方面经常遇到的困惑。对于如何在大学期间提高人际交往能力,我的建议是:

   第一,以诚待人,以责人之心责己、以恕己之心恕人。对别人要抱着诚挚、宽容的胸襟,对自己要怀着自我批评、有过必改的态度。与人交往时,你怎样对待别人,别人也会怎样对待你。这就好比照镜子一样,你自己的表情和态度,可以从他人对你流露出的表情和态度中一览无遗。你若以诚待人,别人也会以诚待你。你若敌视别人,别人也会敌视你。最真挚的友情和最难解的仇恨都是由这种“反射”原理逐步造成的。因此,当你想修正别人时,你应该先修正自己。你想别人怎么对你,你就应该怎么对人。你想他人理解你,你就要首先理解他人。

  
第二,培养真正的友情。如果能做到第一点,很多大学时的朋友就会成为你一辈子的知己。在一起求学和寻求自身发展的道路上,这样的友谊弥足珍贵。交朋友时,不要只去找与你性情相近或只会附和你的人做朋友。好朋友有很多种:乐观的朋友、智慧的朋友、脚踏实地的朋友、幽默风趣的朋友、激励你上进的朋友、提升你能力的朋友、帮你了解自己的朋友、对你说实话的朋友等等。此外,大学时谈恋爱也可以教你如何照顾别人,增进同理心和自控力,但恋爱这件事要随缘,不必为了谈恋爱而谈恋爱。

  
第三,学习团队精神和沟通能力。社团是微观的社会,参与社团是步入社会前最好的磨练。在社团中,可以培养团队合作的能力和领导才能,也可以发挥你的专业特长。但更重要的是,你要做一个诚心诚意的服务者和志愿者,或在担任学生工作时主动扮演同学和老师之间沟通桥梁的角色,并以此锻炼自己的沟通能力,为同学和老师服务。这样的学习过程也不会很轻松,挫折是肯定有的,但是不要灰心,大学社团里的人际交往是一种不用“付学费”的学习,犯了错误也可以重头来过.

   第四,从周围的人身上学习。在班级里、社团中,多观察周围的同学,特别是那些你觉得交往能力和沟通能力特别强的同学,看他们是如何与人相处的。比如,看他们如何处理交往中的冲突、如何说服他人和影响他人、如何发挥自己的合作和协调能力、如何表达对他人的尊重和真诚、如何表示赞许或反对,如何在不冒犯他人的情况下充分展示个性等等。通过观察和模仿,你渐渐地会发现,自己的人际交往能力会有意想不到的改进。在学校里,每一个朋友都可以成为你的良师,他们的热心、幽默、机智、博学、正直、沟通、礼貌等品德都可以成为你的学习对象。同时那些你不喜欢的人和事也可以为你敲响警钟,警告你千万不要做那样的人和事。当然,你也应当慷慨地帮助每一个朋友,试着做他们的良师和模范。

  
第五,提高自身修养和人格魅力。如果觉得没有特长、没有爱好可能会成为自己人际交往能力提高的一个障碍,那么,你可以有意识地去选择和培养一些兴趣爱好。共同的兴趣和爱好也是你与朋友建立深厚感情的途径之一。很多在事业上有所建树的人都不是只会闭门苦读的书呆子,他们大多都有自己的兴趣和爱好。我在微软亚洲研究院的同事中就有绘画、桥牌和体育运动方面的高手。业余爱好不仅是人际交往的一种方式,还可以让大家发掘出自己在读书以外的潜能。例如,体育锻炼既可以发挥你的运动潜能,也可以培养你的团队合作精神。如果真的没有什么兴趣爱好,那么,多读些好书丰富自己的知识也可以改进自己的人际交往能力,因为没有什么比智慧和渊博更能体现一个人的人格魅力了。

   所以,学会与人相处,这也是大学中的一门“必修课”。

 

对大学生们的期望

   踏入大学校门时,你还是一个忙碌的、青涩的、被动的、为分数读书的、被家庭保护着的中学毕业生。

  
就读大学时,你应当掌握七项学习,学好自修之道、基础知识、实践贯通、兴趣培养、积极主动、掌控时间、为人处事。

   经过大学四年,你会从思考中确立自我,从学习中寻求真理,从独立中体验自主,从计划中把握时间,从交流中锻炼表达,从交友中品味成熟,从实践中赢得价值,从兴趣中攫取快乐,从追求中获得力量。

  
离开大学时,只要做到了这些,你最大的收获将是“对什么都可以拥有的自信和渴望”。你就能成为一个有潜力、有思想、有价值、有前途的中国未来的主人翁。

   所以,我认为大学四年应是这样度过。
   所以,我认为大学四年应是这样度过。

 
[pool-1-thread-1] cn.edu.bistu.cs.ir.index.LucenePipeline : 存储博客文章发布时间: ID=16405169, TIME=14972580000,而这篇文章真正的发布时间是2022-06-23 15:03,请根据代码分析为什么会有这种情况package cn.edu.bistu.cs.ir.crawler; import cn.edu.bistu.cs.ir.model.Blog; import cn.edu.bistu.cs.ir.model.BlogStats; import cn.edu.bistu.cs.ir.utils.HttpUtils; import cn.edu.bistu.cs.ir.utils.StringUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import us.codecraft.webmagic.Page; import us.codecraft.webmagic.Site; import us.codecraft.webmagic.processor.PageProcessor; import us.codecraft.webmagic.Request; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.List; import java.util.stream.Collectors; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; /** * 面向博客园(cnblogs.com)的爬虫 * 例如:<a href="https://www.cnblogs.com/tencent-cloud-native/">腾讯云原生</a> * @author chenruoyu */ public class CnBlogsCrawler implements PageProcessor { private final Site site; /** * 博主的ID */ private final String bloggerId; private static final Logger log = LoggerFactory.getLogger(CnBlogsCrawler.class); public static final String RESULT_ITEM_KEY = "BLOG_INFO"; /** * 当前博主的博文目录页URL前缀 */ private final String list_prefix; /** * 当前博主的博文内容页URL前缀 */ private final String blog_prefix; public CnBlogsCrawler(Site site, String bloggerId) { this.site = site; this.bloggerId = bloggerId; //https://www.cnblogs.com/tencent-cloud-native/default.html?page=1 //https://www.cnblogs.com/tencent-cloud-native/p/14913423.html this.list_prefix = String.format("https://www.cnblogs.com/%s/default.html", bloggerId); this.blog_prefix = String.format("https://www.cnblogs.com/%s/p/", bloggerId); } private final SimpleDateFormat sdf = new SimpleDateFormat("uuuu-MM-dd HH:mm"); @Override public void process(Page page) { //url里存储的是请求页面的URL地址 String url = page.getRequest().getUrl(); if (url.startsWith(list_prefix)) { log.info("解析博客目录页[{}]", url); List<String> blogs = page.getHtml().xpath("//div[@class='forFlow']//div[@class='postTitle']/a/@href").all(); log.info("获取博文内容页地址[{}]条", blogs.size()); page.addTargetRequests(blogs); log.info("获取并添加博文目录页分页链接[{}]条", blogs.size()); //TODO 请大家思考如何添加其他博客目录页的地址?需要解析博客目录页最底部的导航栏 int currentPage = 1; //处理页面url //https://www.cnblogs.com/tencent-cloud-native/default.html?page=2 String currentPageParam = page.getUrl().get().replaceAll(".+page=", "").replaceAll("[^\\d]", ""); if (!currentPageParam.isEmpty()) { currentPage = Integer.parseInt(currentPageParam); } // 抓取10个分页 for (int i = currentPage + 1; i <= 11; i++) { String nextPageUrl = list_prefix + "?page=" + i; log.info("添加分页链接: [{}]", nextPageUrl); page.addTargetRequest(new Request(nextPageUrl)); } //setSkip方法可以跳过后续的Pipeline的处理 page.setSkip(true); } else if (url.startsWith(blog_prefix)) { log.info("解析博客内容页[{}]", url); //博文的ID,设置为页面URL删去前缀和 .html后缀后的字符串 String id = url.replace(blog_prefix, "").replace(".html", ""); String title = page.getHtml().xpath("//div[@class='post']/h1[@class='postTitle']//span/text()").get(); String time = page.getHtml().xpath("//div[@class='postDesc']/span[@id='post-date']/text()").get(); String content = page.getHtml().xpath("//div[@class='post']//div[@id='cnblogs_post_body']/allText()").get(); // 抓取标签 //List<String> tags = page.getHtml().xpath("//div[@class='postDesc']/a[@class='postTag']/text()").all(); // 使用AJAX方法获取动态渲染的标签 String tagsJson = getTagsViaAjax(bloggerId); List<String> tags = parseTagsFromJson(tagsJson); Blog blog = new Blog(); blog.setId(id); blog.setTitle(title); blog.setContent(content); blog.setAuthor(bloggerId); blog.setUrl(url); blog.setTags(tags); HttpUtils httpUtils = new HttpUtils(); String json = httpUtils.postJson( String.format("https://www.cnblogs.com/%s/ajax/GetPostStat", bloggerId), String.format("[%s]", id), null); if (!StringUtil.isEmpty(json)) { List<BlogStats> blogStats = JSONObject.parseArray(json, BlogStats.class); if (blogStats != null && blogStats.size() > 0) { log.info("成功获取博文[{}]的阅读数等信息:[{}]", id, json); blog.setBlogStats(blogStats.get(0)); } } // 抓取推荐数和反对数 String recommendJson = httpUtils.getPage(String.format("https://www.cnblogs.com/%s/ajax/GetPostVote?postId=%s", bloggerId, id), null); if (!StringUtil.isEmpty(recommendJson)) { JSONObject recommendObj = JSONObject.parseObject(recommendJson); int recommendCount = recommendObj.getIntValue("diggcount"); int opposeCount = recommendObj.getIntValue("burycount"); blog.setRecommendCount(recommendCount); blog.setOpposeCount(opposeCount); } try { blog.setDate(sdf.parse(time).getTime()); } catch (ParseException e) { log.error("无法识别的日期时间格式:[{}]", time); e.printStackTrace(); blog.setDate(0); } page.putField(RESULT_ITEM_KEY, blog); } else { log.warn("暂不支持的URL地址:[{}]", url); page.setSkip(true); } } @Override public Site getSite() { return this.site; } /** * 通过AJAX获取标签数据 * * @param postId 博文ID * @return 标签的JSON字符串 */ private String getTagsViaAjax(String postId) { //https://www.cnblogs.com/tencent-cloud-native/ajax/widgets String ajaxUrl = String.format("https://www.cnblogs.com/%s/ajax/widgets",postId); HttpUtils httpUtils = new HttpUtils(); return httpUtils.getPage(ajaxUrl, null); } /** * 解析标签的JSON数据 * * @param tagsJson 标签的JSON字符串 * @return 标签列表 */ // 在parseTagsFromJson方法中 private List<String> parseTagsFromJson(String tagsJson) { if (StringUtil.isEmpty(tagsJson)) { return null; } try { // 解析JSON对象 JSONObject jsonObject = JSON.parseObject(tagsJson); // 提取包含标签的HTML部分 String sideColumnHtml = jsonObject.getString("sideColumn"); if (sideColumnHtml == null) { return null; } // 使用Jsoup解析HTML Document doc = Jsoup.parse(sideColumnHtml); // 使用CSS选择器找到所有标签项 Elements tagElements = doc.select("div#sidebar_toptags ul li a"); // 提取标签文本 List<String> tags = tagElements.stream() .map(element -> element.text().split("<")[0].trim()) // 移除可能的HTML片段 .collect(Collectors.toList()); return tags; } catch (Exception e) { log.error("解析标签JSON失败: [{}]", tagsJson); return null; } } }package cn.edu.bistu.cs.ir.index; import cn.edu.bistu.cs.ir.crawler.SinaBlogCrawler; import cn.edu.bistu.cs.ir.model.Blog; import org.apache.lucene.document.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import us.codecraft.webmagic.ResultItems; import us.codecraft.webmagic.Task; import us.codecraft.webmagic.pipeline.Pipeline; import java.util.Date; /** * 基于Lucene的WebMagic Pipeline, * 用于将抓取的数据写入本地的Lucene索引 * @author ruoyuchen */ public class LucenePipeline implements Pipeline { private static final Logger log = LoggerFactory.getLogger(LucenePipeline.class); private final IdxService idxService; public LucenePipeline(IdxService idxService){ log.info("初始化LucenePipeline模块"); this.idxService = idxService; } @Override public void process(ResultItems resultItems, Task task) { Blog blog = resultItems.get(SinaBlogCrawler.RESULT_ITEM_KEY); if(blog==null){ log.error("无法从爬取的结果中提取到Blog对象"); return; } String id = blog.getId(); Document doc = toDoc(blog); boolean result = idxService.addDocument("ID", id, doc); if(!result){ log.error("无法将ID为[{}]的博客内容写入索引", id); }else { log.info("成功将ID为[{}]的博客内容写入索引", id); } } private Document toDoc(Blog blog) { Document document = new Document(); // 页面ID document.add(new StringField("ID", blog.getId(), Field.Store.YES)); // 页面标题 document.add(new TextField("TITLE", blog.getTitle(), Field.Store.YES)); // 页面内容全文 document.add(new TextField("CONTENT", blog.getContent(), Field.Store.YES)); document.add(new TextField("AUTHOR", blog.getAuthor(), Field.Store.YES)); // 发布时间 long dateLong = blog.getDate(); if (dateLong != 0) { document.add(new LongPoint("TIME", dateLong)); // 存储时间戳以便检索 document.add(new StoredField("TIME_STORED", dateLong)); // 存储时间戳以便后续使用 log.info("存储博客文章发布时间: ID={}, TIME={}", blog.getId(), dateLong); } else { log.warn("!!!!!!!!!!!!博客文章ID={}的时间戳为空!!!!!!!!!!!!!", blog.getId()); } // 标签 if (blog.getTags() != null && !blog.getTags().isEmpty()) { for (String tag : blog.getTags()) { document.add(new TextField("TAG", tag, Field.Store.YES)); } } // 分类 if (blog.getCategory() != null) { document.add(new StringField("CATEGORY", blog.getCategory(), Field.Store.YES)); } return document; } // 工具函数:把 null 变成空字符串,防止 Lucene 报错 private String safeString(String input) { return input == null ? "" : input; } }package cn.edu.bistu.cs.ir.crawler; import cn.edu.bistu.cs.ir.model.Blog; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import us.codecraft.webmagic.Page; import us.codecraft.webmagic.Site; import us.codecraft.webmagic.processor.PageProcessor; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.List; /** * 面向新浪博客 * (例如<a href="http://blog.sina.com.cn/kaifulee">李开复的新浪博客</a>) * 的网络爬虫示例 * @author ruoyuchen */ public class SinaBlogCrawler implements PageProcessor { private static final String URL_PREFIX_LIST = "http://blog.sina.com.cn/s/articlelist_"; private static final String URL_PREFIX_BLOG = "http://blog.sina.com.cn/s/blog_"; public static final String RESULT_ITEM_KEY = "BLOG_INFO"; private final Site site; /** * 博主的ID */ private final String blogger; public SinaBlogCrawler(Site site, String blogger){ this.site = site; this.blogger = blogger; } private static final Logger log = LoggerFactory.getLogger(SinaBlogCrawler.class); private final SimpleDateFormat sdf = new SimpleDateFormat("(uuuu-MM-dd HH:mm:ss)"); @Override public void process(Page page) { String url = page.getRequest().getUrl(); if(url.startsWith(URL_PREFIX_LIST)){ log.info("解析博客目录页[{}]", url); List<String> blogs = page.getHtml().xpath("//div[@class='SG_connBody']//div[@class='articleList']//a/@href").all(); log.info("获取博文内容页地址[{}]条", blogs.size()); page.addTargetRequests(blogs); List<String> lists = page.getHtml().xpath("//div[@class='SG_connBody']//div[@class='SG_page']//li/a/@href").all(); log.info("获取博文目录页地址[{}]条", lists.size()); page.addTargetRequests(lists); //setSkip方法可以跳过后续的Pipeline的处理 page.setSkip(true); }else if(url.startsWith(URL_PREFIX_BLOG)){ log.info("解析博客内容页[{}]", url); //博文的ID,设置为页面URL删去前缀和 .html后缀后的字符串 String id = url.replace(URL_PREFIX_BLOG, "").replace(".html", ""); String title = page.getHtml().xpath("//div[@id='articlebody']//div[@class='articalTitle']/h2/text()").get(); String time = page.getHtml().xpath("//div[@id='articlebody']//div[@class='articalTitle']/span/text()").get(); String content = page.getHtml().xpath("//div[@id='articlebody']//div[@id='sina_keyword_ad_area2']/tidyText()").get(); //TODO 请大家思考如何抓取页面中的标签、分类,阅读数、收藏数等数据 Blog blog = new Blog(); blog.setId(id); blog.setTitle(title); blog.setContent(content); try { blog.setDate(sdf.parse(time).getTime()); } catch (ParseException e) { log.error("无法识别的日期时间格式:[{}]", time); e.printStackTrace(); blog.setDate(0); } blog.setAuthor(blogger); page.putField(RESULT_ITEM_KEY, blog); }else{ log.warn("暂不支持的URL地址:[{}]", url); page.setSkip(true); } } @Override public Site getSite() { return this.site; } }
05-23
package cn.edu.bistu.cs.ir.controller; import cn.edu.bistu.cs.ir.index.LucenePipeline; import cn.edu.bistu.cs.ir.index.IdxService; import cn.edu.bistu.cs.ir.utils.QueryResponse; import org.apache.lucene.document.Document; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.*; /** 面向检索服务接口的控制器类 Restful Web Services/Rest风格的Web服务 @author chenruoyu */ @RestController @RequestMapping(“/query”) public class QueryController { private static final Logger log = LoggerFactory.getLogger(QueryController.class); private final IdxService idxService; public QueryController(@Autowired IdxService idxService){ this.idxService = idxService; } /** 根据关键词对索引进行分页检索, 根据页号和页面大小, 返回指定页的数据记录 @param kw 待检索的关键词 @param pageNo 页号,默认为1 @param pageSize 页的大小,默认为10 @return 检索得到的结果记录,以<页面ID, 页面标题>二元组的形式返回 */ @GetMapping(value = “/kw”, produces = “application/json;charset=UTF-8”) public QueryResponse<List<Map<String, String>>> queryByKw(@RequestParam(name = “kw”) String kw, @RequestParam(name = “pageNo”, defaultValue = “1”) int pageNo, @RequestParam(name = “pageSize”, defaultValue = “10”) int pageSize){ try { // 计算起始位置 int start = (pageNo - 1) * pageSize; log.debug(“Querying with kw: {}, start: {}, pageSize: {}”, kw, start, pageSize); //TODO 请大家思考如何在queryByKw函数中添加分页参数 List<Document> docs = idxService.queryByKw(kw,start,pageSize); List<Map<String, String>> results = new ArrayList<>(); for(Document doc : docs){ Map<String, String> record = new HashMap<>(2); record.put("ID", doc.get("ID")); record.put("TITLE", doc.get("TITLE")); record.put("AUTHOR", doc.get("AUTHOR")); // 获取并转换发布时间 // String publishDateStr = doc.get("TIME_STORED"); String publishDateStr = doc.get("TIME_STORED"); if (publishDateStr != null) { try { long publishDateLong = Long.parseLong(publishDateStr); Date publishDate = new Date(publishDateLong); record.put("TIME", new SimpleDateFormat("yyyy-MM-dd HH:mm").format(publishDate)); } catch (NumberFormatException e) { log.error("无法将发布时间转换为长整型: {}", publishDateStr, e); record.put("TIME", "未知"); } } else { // 如果TIME_STORED为空,检查TIME字段是否存在 if (doc.get("TIME") != null) { log.info("TIME_STORED字段缺失,但存在TIME字段: ID={}", doc.get("ID")); record.put("TIME", "未记录具体时间"); } else { log.info("博客文章ID={}的发布时间信息缺失", doc.get("ID")); record.put("TIME", "weizhi"); } } results.add(record); } if (results.isEmpty()) { return QueryResponse.genErr("未找到相关记录"); } return QueryResponse.genSucc("检索成功", results); } catch (Exception e) { log.error(“检索过程中发生异常:[{}]”, e.getMessage()); return QueryResponse.genErr(“检索过程中发生异常”); } } }package cn.edu.bistu.cs.ir.index; import cn.edu.bistu.cs.ir.crawler.SinaBlogCrawler; import cn.edu.bistu.cs.ir.model.Blog; import org.apache.lucene.document.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import us.codecraft.webmagic.ResultItems; import us.codecraft.webmagic.Task; import us.codecraft.webmagic.pipeline.Pipeline; import java.util.Date; /** 基于Lucene的WebMagic Pipeline, 用于将抓取的数据写入本地的Lucene索引 @author ruoyuchen */ public class LucenePipeline implements Pipeline { private static final Logger log = LoggerFactory.getLogger(LucenePipeline.class); private final IdxService idxService; public LucenePipeline(IdxService idxService){ log.info(“初始化LucenePipeline模块”); this.idxService = idxService; } @Override public void process(ResultItems resultItems, Task task) { Blog blog = resultItems.get(SinaBlogCrawler.RESULT_ITEM_KEY); if(blog==null){ log.error(“无法从爬取的结果中提取到Blog对象”); return; } String id = blog.getId(); Document doc = toDoc(blog); boolean result = idxService.addDocument(“ID”, id, doc); if(!result){ log.error(“无法将ID为[{}]的博客内容写入索引”, id); }else { log.info(“成功将ID为[{}]的博客内容写入索引”, id); } } private Document toDoc(Blog blog) { Document document = new Document(); // 页面ID document.add(new StringField("ID", blog.getId(), Field.Store.YES)); // 页面标题 document.add(new TextField("TITLE", blog.getTitle(), Field.Store.YES)); // 页面内容全文 document.add(new TextField("CONTENT", blog.getContent(), Field.Store.YES)); document.add(new TextField("AUTHOR", blog.getAuthor(), Field.Store.YES)); // 发布时间 long dateLong = blog.getDate(); if (dateLong != 0) { document.add(new LongPoint("TIME", dateLong)); // 存储时间戳以便检索 document.add(new StoredField("TIME_STORED", dateLong)); // 存储时间戳以便后续使用 log.info("存储博客文章发布时间: ID={}, TIME={}", blog.getId(), dateLong); } else { log.warn("!!!!!!!!!!!!博客文章ID={}的时间戳为空!!!!!!!!!!!!!", blog.getId()); } // 标签 if (blog.getTags() != null && !blog.getTags().isEmpty()) { for (String tag : blog.getTags()) { document.add(new TextField("TAG", tag, Field.Store.YES)); } } // 分类 if (blog.getCategory() != null) { document.add(new StringField("CATEGORY", blog.getCategory(), Field.Store.YES)); } return document; } // 工具函数:把 null 变成空字符串,防止 Lucene 报错 private String safeString(String input) { return input == null ? “” : input; } }package cn.edu.bistu.cs.ir.index; import cn.edu.bistu.cs.ir.config.Config; import cn.edu.bistu.cs.ir.utils.StringUtil; import com.hankcs.lucene.HanLPAnalyzer; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.; import org.apache.lucene.index.; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.*; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.IOException; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BooleanClause.Occur; /** 面向<a href=“https://lucene.apache.org/”>Lucene</a> 索引读、写的服务类 @author chenruoyu */ @Component public class IdxService implements DisposableBean { private static final Logger log = LoggerFactory.getLogger(IdxService.class); private static final Class<? extends Analyzer> DEFAULT_ANALYZER = StandardAnalyzer.class; private IndexWriter writer; public IdxService(@Autowired Config config) throws Exception { Analyzer analyzer = DEFAULT_ANALYZER.getConstructor().newInstance(); Directory index; try { index = FSDirectory.open(Paths.get(config.getIdx())); IndexWriterConfig writerConfig = new IndexWriterConfig(analyzer); writer = new IndexWriter(index, writerConfig); log.info(“索引初始化完成,索引目录为:[{}]”, config.getIdx()); } catch (IOException e) { e.printStackTrace(); log.error(“无法初始化索引,请检查提供的索引目录是否可用:[{}]”, config.getIdx()); writer = null; } } public boolean addDocument(String idFld, String id, Document doc){ if(writer==null||doc==null){ log.error(“Writer对象或文档对象为空,无法添加文档到索引中”); return false; } if(StringUtil.isEmpty(idFld)||StringUtil.isEmpty(id)){ log.error(“ID字段名或ID字段值为空,无法添加文档到索引中”); return false; } try { writer.updateDocument(new Term(idFld, id), doc); writer.commit(); log.info(“成功将ID为[{}]的文档加入索引”, id); return true; } catch (IOException e) { e.printStackTrace(); log.error(“构建索引失败”); return false; } } /** 获取索引中文档总数 */ public int getTotalDocuments() throws IOException { IndexReader reader = DirectoryReader.open(writer); return reader.numDocs(); } /** 根据关键词对索引内容进行检索,并将检索结果返回 @param kw 待检索的关键词 @return 检索得到的文档列表 */ public List<Document> queryByKw(String kw,int start,int pageSize) throws Exception{ //打开准实时索引Reader DirectoryReader reader = DirectoryReader.open(writer); IndexSearcher searcher = new IndexSearcher(reader); Analyzer analyzer = new HanLPAnalyzer(); //Analyzer analyzer = DEFAULT_ANALYZER.getConstructor().newInstance(); QueryParser parser = new QueryParser(“TITLE”, analyzer); Query query = parser.parse(kw); log.debug(“Generated query: {}”, query.toString()); // 执行搜索并获取TopDocs TopDocs docs = searcher.search(query, start + pageSize); ScoreDoc[] hits = docs.scoreDocs; List<Document> results = new ArrayList<>(); for (int i = Math.min(start, hits.length); i < Math.min(hits.length, start + pageSize); i++) { results.add(searcher.doc(hits[i].doc)); } return results; } //TODO 请大家在这里添加更多的检索函数,如针对发表时间的范围检索等, // 添加了检索函数后,还需要相应地在Controller中添加接口 /// /////////////////////////////// public List<Map<String, String>> queryByTitleOrProductName(String keyword) throws Exception { IndexReader reader = DirectoryReader.open(writer); IndexSearcher searcher = new IndexSearcher(reader); StandardAnalyzer analyzer = new StandardAnalyzer(); QueryParser parser = new QueryParser(“TITLE”, analyzer); Query query = new TermQuery(new Term(“TITLE”, keyword)); // 也可以根据需求添加商品名称的检索逻辑 // Query query = parser.parse("TITLE:" + keyword + " OR PRODUCT_NAME:" + keyword); TopDocs docs = searcher.search(query, 10); ScoreDoc[] hits = docs.scoreDocs; List<Map<String, String>> results = new ArrayList<>(); for (ScoreDoc doc : hits) { Document document = searcher.doc(doc.doc); Map<String, String> result = new HashMap<>(); result.put("ID", document.get("ID")); result.put("TITLE", document.get("TITLE")); results.add(result); } return results; } /// ////////////////////// /** 使用BooleanQuery实现复合检索 */ public List<Map<String, String>> booleanSearch(String titleKeyword, String contentKeyword) throws Exception { IndexReader reader = DirectoryReader.open(writer); IndexSearcher searcher = new IndexSearcher(reader); BooleanQuery.Builder builder = new BooleanQuery.Builder(); if (titleKeyword != null && !titleKeyword.isEmpty()) { builder.add(new TermQuery(new Term(“TITLE”, titleKeyword)), Occur.MUST); } if (contentKeyword != null && !contentKeyword.isEmpty()) { builder.add(new TermQuery(new Term(“CONTENT”, contentKeyword)), Occur.MUST); } Query query = builder.build(); TopDocs docs = searcher.search(query, 10); ScoreDoc[] hits = docs.scoreDocs; List<Map<String, String>> results = new ArrayList<>(); for (ScoreDoc doc : hits) { Document document = searcher.doc(doc.doc); Map<String, String> result = new HashMap<>(); result.put(“ID”, document.get(“ID”)); result.put(“TITLE”, document.get(“TITLE”)); results.add(result); } return results; } /** 使用QueryParser实现复合检索 */ public List<Map<String, String>> queryParserSearch(String queryString) throws Exception { IndexReader reader = DirectoryReader.open(writer); IndexSearcher searcher = new IndexSearcher(reader); QueryParser parser = new QueryParser(“TITLE”, new StandardAnalyzer()); Query query = parser.parse(queryString); TopDocs docs = searcher.search(query, 10); ScoreDoc[] hits = docs.scoreDocs; List<Map<String, String>> results = new ArrayList<>(); for (ScoreDoc doc : hits) { Document document = searcher.doc(doc.doc); Map<String, String> result = new HashMap<>(); result.put(“ID”, document.get(“ID”)); result.put(“TITLE”, document.get(“TITLE”)); results.add(result); } return results; } // 在IdxService类中添加一个方法来打印文档总数 public void printTotalDocuments() { try { int total = getTotalDocuments(); System.out.println("索引中文档总数: " + total); } catch (IOException e) { e.printStackTrace(); } } public void printDocumentDetails() { try { DirectoryReader reader = DirectoryReader.open(writer); IndexSearcher searcher = new IndexSearcher(reader); int total = reader.numDocs(); log.info(“索引中文档总数: {}”, total); for (int i = 0; i < total; i++) { Document doc = searcher.doc(i); log.info("文档ID: {}, TITLE: {}, TIME_STORED: {}", doc.get("ID"), doc.get("TITLE"), doc.get("TIME_STORED")); } } catch (IOException e) { e.printStackTrace(); log.error(“获取索引文档详情时发生异常”); } } @Override public void destroy(){ if(this.writer==null){ return; } try { log.info(“索引关闭”); writer.close(); } catch (IOException e) { e.printStackTrace(); log.info(“尝试关闭索引失败”); } } } package cn.edu.bistu.cs.ir.crawler; import cn.edu.bistu.cs.ir.model.Blog; import cn.edu.bistu.cs.ir.model.BlogStats; import cn.edu.bistu.cs.ir.utils.HttpUtils; import cn.edu.bistu.cs.ir.utils.StringUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import us.codecraft.webmagic.Page; import us.codecraft.webmagic.Site; import us.codecraft.webmagic.processor.PageProcessor; import us.codecraft.webmagic.Request; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.List; import java.util.stream.Collectors; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; /** 面向博客园(cnblogs.com)的爬虫 例如:<a href=“https://www.cnblogs.com/tencent-cloud-native/”>腾讯云原生</a> @author chenruoyu */ public class CnBlogsCrawler implements PageProcessor { private final Site site; /** 博主的ID */ private final String bloggerId; private static final Logger log = LoggerFactory.getLogger(CnBlogsCrawler.class); public static final String RESULT_ITEM_KEY = “BLOG_INFO”; /** 当前博主的博文目录页URL前缀 */ private final String list_prefix; /** 当前博主的博文内容页URL前缀 */ private final String blog_prefix; public CnBlogsCrawler(Site site, String bloggerId) { this.site = site; this.bloggerId = bloggerId; //https://www.cnblogs.com/tencent-cloud-native/default.html?page=1 //https://www.cnblogs.com/tencent-cloud-native/p/14913423.html this.list_prefix = String.format(“https://www.cnblogs.com/%s/default.html”, bloggerId); this.blog_prefix = String.format(“https://www.cnblogs.com/%s/p/”, bloggerId); } private final SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm”); @Override public void process(Page page) { //url里存储的是请求页面的URL地址 String url = page.getRequest().getUrl(); if (url.startsWith(list_prefix)) { log.info(“解析博客目录页[{}]”, url); List<String> blogs = page.getHtml().xpath(“//div[@class=‘forFlow’]//div[@class=‘postTitle’]/a/@href”).all(); log.info(“获取博文内容页地址[{}]条”, blogs.size()); page.addTargetRequests(blogs); log.info(“获取并添加博文目录页分页链接[{}]条”, blogs.size()); //TODO 请大家思考如何添加其他博客目录页的地址?需要解析博客目录页最底部的导航栏 int currentPage = 1; //处理页面url //https://www.cnblogs.com/tencent-cloud-native/default.html?page=2 String currentPageParam = page.getUrl().get().replaceAll(“.+page=”, “”).replaceAll(“\d”, “”); if (!currentPageParam.isEmpty()) { currentPage = Integer.parseInt(currentPageParam); } // 抓取10个分页 for (int i = currentPage + 1; i <= 11; i++) { String nextPageUrl = list_prefix + "?page=" + i; log.info("添加分页链接: [{}]", nextPageUrl); page.addTargetRequest(new Request(nextPageUrl)); } //setSkip方法可以跳过后续的Pipeline的处理 page.setSkip(true); } else if (url.startsWith(blog_prefix)) { log.info("解析博客内容页[{}]", url); //博文的ID,设置为页面URL删去前缀和 .html后缀后的字符串 String id = url.replace(blog_prefix, "").replace(".html", ""); String title = page.getHtml().xpath("//div[@class='post']/h1[@class='postTitle']//span/text()").get(); String time = page.getHtml().xpath("//div[@class='postDesc']/span[@id='post-date']/text()").get(); String content = page.getHtml().xpath("//div[@class='post']//div[@id='cnblogs_post_body']/allText()").get(); List<String> tags = page.getHtml().xpath("//div[@class='postBody']//div[@id='EntryTag']/a/text()").all(); // 抓取标签 //List<String> tags = page.getHtml().xpath("//div[@id='EntryTag']/a/text()").all(); // 使用AJAX方法获取动态渲染的标签 /* String tagsJson = getTagsViaAjax(bloggerId); List<String> tags = parseTagsFromJson(tagsJson);*/ Blog blog = new Blog(); blog.setId(id); blog.setTitle(title); blog.setContent(content); blog.setAuthor(bloggerId); blog.setUrl(url); blog.setTags(tags); HttpUtils httpUtils = new HttpUtils(); String json = httpUtils.postJson( String.format("https://www.cnblogs.com/%s/ajax/GetPostStat", bloggerId), String.format("[%s]", id), null); if (!StringUtil.isEmpty(json)) { List<BlogStats> blogStats = JSONObject.parseArray(json, BlogStats.class); if (blogStats != null && blogStats.size() > 0) { log.info("成功获取博文[{}]的阅读数等信息:[{}]", id, json); blog.setBlogStats(blogStats.get(0)); } } // 抓取推荐数和反对数 String recommendJson = httpUtils.getPage(String.format("https://www.cnblogs.com/%s/ajax/GetPostVote?postId=%s", bloggerId, id), null); if (!StringUtil.isEmpty(recommendJson)) { JSONObject recommendObj = JSONObject.parseObject(recommendJson); int recommendCount = recommendObj.getIntValue("diggcount"); int opposeCount = recommendObj.getIntValue("burycount"); blog.setRecommendCount(recommendCount); blog.setOpposeCount(opposeCount); } try { blog.setDate(sdf.parse(time).getTime()); } catch (ParseException e) { log.info("无法识别的日期时间格式:[{}]", time); e.printStackTrace(); blog.setDate(0); } page.putField(RESULT_ITEM_KEY, blog); } else { log.warn("暂不支持的URL地址:[{}]", url); page.setSkip(true); } } @Override public Site getSite() { return this.site; } /** 通过AJAX获取标签数据 @param postId 博文ID @return 标签的JSON字符串 */ private String getTagsViaAjax(String postId) { //https://www.cnblogs.com/tencent-cloud-native/ajax/widgets String ajaxUrl = String.format(“https://www.cnblogs.com/%s/ajax/widgets”,postId); HttpUtils httpUtils = new HttpUtils(); return httpUtils.getPage(ajaxUrl, null); } /** 解析标签的JSON数据 @param tagsJson 标签的JSON字符串 @return 标签列表 */ // 在parseTagsFromJson方法中 private List<String> parseTagsFromJson(String tagsJson) { if (StringUtil.isEmpty(tagsJson)) { return null; } try { // 解析JSON对象 JSONObject jsonObject = JSON.parseObject(tagsJson); // 提取包含标签的HTML部分 String sideColumnHtml = jsonObject.getString(“sideColumn”);// if (sideColumnHtml == null) { return null; } // 使用Jsoup解析HTML Document doc = Jsoup.parse(sideColumnHtml); // 使用CSS选择器找到所有标签项 Elements tagElements = doc.select(“div#sidebar_toptags ul li a”); // 提取标签文本 List<String> tags = tagElements.stream() .map(element -> element.text().split(“<”)[0].trim()) // 移除可能的HTML片段 .collect(Collectors.toList()); return tags; } catch (Exception e) { log.error(“解析标签JSON失败: [{}]”, tagsJson); return null; } } }package cn.edu.bistu.cs.ir.crawler; import cn.edu.bistu.cs.ir.model.Blog; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import us.codecraft.webmagic.Page; import us.codecraft.webmagic.Site; import us.codecraft.webmagic.processor.PageProcessor; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.List; /** 面向新浪博客 (例如<a href=“http://blog.sina.com.cn/kaifulee”>李开复的新浪博客</a>) 的网络爬虫示例 @author ruoyuchen */ public class SinaBlogCrawler implements PageProcessor { private static final String URL_PREFIX_LIST = “http://blog.sina.com.cn/s/articlelist_”; private static final String URL_PREFIX_BLOG = “http://blog.sina.com.cn/s/blog_”; public static final String RESULT_ITEM_KEY = “BLOG_INFO”; private final Site site; /** 博主的ID */ private final String blogger; public SinaBlogCrawler(Site site, String blogger){ this.site = site; this.blogger = blogger; } private static final Logger log = LoggerFactory.getLogger(SinaBlogCrawler.class); private final SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm”); @Override public void process(Page page) { String url = page.getRequest().getUrl(); if(url.startsWith(URL_PREFIX_LIST)){ log.info(“解析博客目录页[{}]”, url); List<String> blogs = page.getHtml().xpath(“//div[@class=‘SG_connBody’]//div[@class=‘articleList’]//a/@href”).all(); log.info(“获取博文内容页地址[{}]条”, blogs.size()); page.addTargetRequests(blogs); List<String> lists = page.getHtml().xpath(“//div[@class=‘SG_connBody’]//div[@class=‘SG_page’]//li/a/@href”).all(); log.info(“获取博文目录页地址[{}]条”, lists.size()); page.addTargetRequests(lists); //setSkip方法可以跳过后续的Pipeline的处理 page.setSkip(true); }else if(url.startsWith(URL_PREFIX_BLOG)){ log.info(“解析博客内容页[{}]”, url); //博文的ID,设置为页面URL删去前缀和 .html后缀后的字符串 String id = url.replace(URL_PREFIX_BLOG, “”).replace(“.html”, “”); String title = page.getHtml().xpath(“//div[@id=‘articlebody’]//div[@class=‘articalTitle’]/h2/text()”).get(); String time = page.getHtml().xpath(“//div[@id=‘articlebody’]//div[@class=‘articalTitle’]/span/text()”).get(); String content = page.getHtml().xpath(“//div[@id=‘articlebody’]//div[@id=‘sina_keyword_ad_area2’]/tidyText()”).get(); //TODO 请大家思考如何抓取页面中的标签、分类,阅读数、收藏数等数据 List<String> tags = page.getHtml().xpath(“//div[@class=‘postBody’]//div[@id=‘EntryTag’]/a/text()”).all(); Blog blog = new Blog(); blog.setId(id); blog.setTitle(title); blog.setContent(content); blog.setTags(tags); try { blog.setDate(sdf.parse(time).getTime()); } catch (ParseException e) { log.error(“无法识别的日期时间格式:[{}]”, time); e.printStackTrace(); blog.setDate(0); } blog.setAuthor(blogger); page.putField(RESULT_ITEM_KEY, blog); }else{ log.warn(“暂不支持的URL地址:[{}]”, url); page.setSkip(true); } } @Override public Site getSite() { return this.site; } }package cn.edu.bistu.cs.ir.crawler; import cn.edu.bistu.cs.ir.config.Config; import cn.edu.bistu.cs.ir.index.IdxService; import cn.edu.bistu.cs.ir.index.LucenePipeline; import cn.edu.bistu.cs.ir.utils.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import us.codecraft.webmagic.Site; import us.codecraft.webmagic.Spider; import us.codecraft.webmagic.pipeline.JsonFilePipeline; import javax.annotation.PostConstruct; import static us.codecraft.webmagic.Spider.Status.Stopped; /** 面向爬虫的服务类 @author chenruoyu */ @Component public class CrawlerService { private static final Logger log = LoggerFactory.getLogger(CrawlerService.class); private final Config config; private final IdxService idxService; public CrawlerService(@Autowired Config config, @Autowired IdxService idxService) { this.config = config; this.idxService = idxService; } private Spider spider = null; /** 启动面向博客园的爬虫 @param blogger 待爬取的博主ID */ public void startCnBlogCrawler(String blogger) { if (StringUtil.isEmpty(blogger)) { log.error(“博主的唯一ID不可以为空”); return; } String startPage = String.format(“https://www.cnblogs.com/%s/default.html?page=2”, blogger); if (this.spider != null) { if (!Stopped.equals(this.spider.getStatus())) { //如果spider成员不为空,并且状态不是 Stopped,则不可以启动新的爬虫 log.error(“当前有正在运行的爬虫对象,不可以创建新的爬虫”); return; } } Site site = Site .me() .setRetryTimes(config.getRetryTimes()) .setSleepTime(config.getSleepTime()) .setUserAgent(config.getAgent()); this.spider = Spider.create(new CnBlogsCrawler(site, blogger)); spider.addPipeline(new LucenePipeline(idxService)); spider.addPipeline(new JsonFilePipeline(config.getCrawler())); spider.thread(1); spider.addUrl(startPage); spider.runAsync(); log.info(“启动面向博客园的爬虫,抓取博主ID为[{}]的作者的文章”, blogger); } @PostConstruct public void init() { if (config.isStartCrawler()) { log.info(“系统配置信息中[startCrawler]配置项为true,启动爬虫的运行”); startCnBlogCrawler(“tencent-cloud-native”); } } }为什么在datelong非空的前提下publishDateStr会是空的?且有的值为"TIME": "1970-09-08 17:44"和"1970-01-24 09:57"这种
最新发布
05-26
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值