-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.json
More file actions
1 lines (1 loc) · 102 KB
/
Copy pathindex.json
File metadata and controls
1 lines (1 loc) · 102 KB
1
[{"content":"\r前言\r#\r由于大模型的出现,软件开发产生了革命性的变化,当前prompt+Agent的使用大大提升了开发效率,欢迎来到 Vibe Coding 时代!\n回想起当年刚入学时使用VC6.0手搓C代码的窘迫、问题得不到解答的苦闷,生活在Vibe Coding 时代是多么的幸福啊! 当然,很多人可能感觉不到幸福,而是感觉到焦虑,感觉自己要被 AI 取代从而面临失业了。 今天我在这里不会展开讨论这个话题,因为我对这个问题的看法是——尽人事,听天命。\n天命不由己,让我们来聊聊自己能做的人事 。\n保持身心健康\r#\r保持身心健康是重中之重,我把它放到了第一位,因为没有了身心健康,其他的都是空谈。\n如何保持身心健康?\n首先是不要焦虑。不需要对Vibe Coding那么恐惧,既来之,则安之。先接受它的存在,然后寻求共存的方式。 当然,你可能会说,我没法不焦虑,一旦 AI 取代了我,我就要失业,我就会面临各种经济压力。 OK,那就允许AI取代你这个事情发生,经济上的事情做好风控和资产重组,放松心态。 佛说:“无我相,无法相,无众生相。” 没必要执着于自己一定要怎样,保持心态开放,拥抱变化,活出多元化的自我。\n然后是保持充足的睡眠。 对于我们这一行,这个还是很重要的。加班多了,回家就别刷手机、打游戏了,老老实实去睡觉。 睡眠不足心率都会加快的,同时会引发一大堆的身体的问题。能好好睡觉本身也是一种幸福,也是身体健康的重要特征。 多爱惜自己的身体,多呵护自己好好睡觉的能力。\n最后是多锻炼。 这个老生常谈,但是很重要。锻炼要找到自己的节奏,简单蹦蹦跳跳、拉伸一下、快走一下都是锻炼,培养自己的执行力才是锻炼需要的最核心的能力,Just Do It。\n要好好地活着,才能讨论怎样活呀。\n接触高质量信息\r#\r任何时代,高质量信息都至关重要,在信息爆炸的时代更甚。\n我们要认识到自己的精力有限。 就算我们拥有良好的心态,健康的身体,我们一天也就24个小时, 其中高质量接触和处理信息的时间估计不会超过12个小时。 我们的注意力是很宝贵的资源,正是因为它宝贵,我们更需要把有限的注意力放在高质量的信息上。\n那么什么是高质量的信息?我认为有以下几个切入点:\n一手信息或原始信息。 常见的有:官方信息、源代码、论文 充分思考的信息。 常见的有:逻辑推理严密的信息(反之是情绪化的表达)、制度化的信息(反之是个体化的诉求) 当然,我说的这些优秀信息不是说要埋头钻进去,而是用来关注和查阅的。更多详细内容下一个主题讲。\n为什么这些是高质量信息?\n先说一手信息或原始信息。这些信息更接近本质,换个说法也可以说更接近事实。 我现在的观点是:先了解清楚事实,再讨论基于事实的认知。 同一个事实不同人会有不同的认知,会有不同的建模和解构的方法。 如果你只是掌握这些建模和解构方法,而忽略底层的事实,大部分情况下你都没法理解和记忆对应的知识,更加无从谈起融入自己的理解和认知。\n我举两个我熟悉的领域的例子:\n编程:比如很多文章会跟你说,某个库不能怎样怎样用,这种就是非原始信息,这个是作者的认知结果,大概率在作者的场景下是正确的。但是,你应该了解是一手信息,也就是这个库的源代码是怎样实现的,为什么会出现这这种结果?你的场景是否有这个问题? 交易:撮合成交数据就是原始的信息,基于成交数据统计的各种指标就是加工后的信息,各种大V的解读和观点更是三手四手的信息。做交易,多了解成交数据的更重要,因为他更原始更本质,也能够让你更理解一些加工后的信息,也能帮助你构建自己的交易理念和体系。 我解决过的不少BUG都是看源代码实现定位解决的,搜索引擎咋搜都找不到结果,或者结果跟我场景相去甚远。\n目前印象深刻的就是一个mysql同步ES的工具monstache,一个是influxdb的数据处理工具kapacitor\n好的,说完为什么一手信息或原始信息是高质量信息,我们再说一下为什么充分思考的信息是高质量的信息。\n因为充分思考这个过程需要时间成本,人只要会承担相应的代价,就会认真考虑自己的言行。 毕竟有句话叫做“成年人不做改变,只做筛选”,有充分的思考就是一个很好的筛选手段。 当然,这句话也是可以扩展的,比如真枪实弹地干过的也是一个很好的筛选手段,知易行难。 总得来说要灵活掌握,有效筛选,避免浪费自己的精力。\n在体验和实践中学习\r#\r作为一名并不优秀的小镇做题家,回顾我的学习经历,大部分时间都是在接受一些概念的定义,很少有体验和实践的机会。 就算是读大学的时候,老师更多的都是在讲一些概念定义,鲜有直接的体验和实践辅导。就算自己去体验和实践了, 很多问题也得不到解答。这里我并非抹黑我的母校,可以说这个是高校教育普遍的问题。 我的结论也并非凭空捏造,而是我综合自身的经历、多方朋友的交流、网上博主的输出、活动嘉宾的分享做出的判断。 不保证结论一定正确,但是保证是充分思考的信息。\n可能我上面说得比较抽象,你没有get到我的意思,没关系,下面我举一个例子你就明白了:\n想象一下你现在要学习Vibe Coding,传统的教育会怎么跟你讲?\n学习定义。什么是vibe coding?什么是大模型?什么是Agent?什么是Skill? 什么是MCP? 然后都给出一个形式化的,很严谨的定义。 然后开始基于这些定义来做题,接着考试。 总的来说就是很抽象,概念学了很多,但是可能完全没有实践过。我其实是理解为什么会出现这种情况的, 主要还是受限于成本,体验和实践的启动本身就需要成本,过程中的试错可能还需要额外的成本。 在一个成本有限的条件下,优先投入时间到成本很低的理论学习是能理解的。\n但是,我们要自己找机会多去体验和实践,不能拘泥于理论,就算成本有限,也还是有一些优秀的体验和实践的资源的, 而且最重要的是——大模型把这个过程的成本压缩得很低了。\n我们回到主题:要从体验和实践中学习vibe coding是怎样的?\n下载一个工具,Claude Code / Codex / OpenCode(已拉黑OpenCode,太拉了,见嘴替文章OpenCode狗都不用.我这不是跟风,我也是深度使用过OpenCode的,文章里面的问题深!有!体!会!) 接入一个领先的模型,Opus / GPT / GLM 找一个需求 开始做需求 不懂就问大模型,过程中的问题和思考记录一下 解决需求,收集问题,总结经验。OK你对Vibe Coding 就有了一个直观地感受 你会发现走一把这个流程,很多概念你已经有印象了,只是可能缺少一个定义,哦,原来这东西就叫Agent、SKill、MCP。 知识无形中就进入了你的脑子,抽象的概念也有了落地的实体。\n从前走这条路可能有些困难,因为有时候自己甚至都不知道怎样实践,实践中遇到的很多问题都无人解答, 往往在自我怀疑中就丧失前进的动力。但是,大人,时代变了。有了大模型,这些都不是问题, 大模型可以给你输出优秀的学习路径,可以按照你的想法给你问题的答案,这种问有所答的感觉,真的太!爽!了!\n最后我想说,我并非否认形式化的重要性和理论的定义对于沟通的方便性,但是这些都是体验和实践后,对相关概念有感觉后的事情, 而不是学习相关新知识的方法。人类科学大部分的东西都是先有现象,后有的理论,我们不能本末倒置。我现在也很喜欢研究一样东西的历史, 比如AI的发展史,也想着去复习一下自动机和形式化理论,但是这些都是我大量体验和实践后自己自发的想法, 而不是我刚入门时候被动去接受的枯燥理论。\nAnyway,记住在体验和实践中学习,只要上道了,其他很多东西都是顺其自然的。\n抽象思维和领域知识\r#\r不是吧,哥们,你前面不是才跟我说理论很抽象要联系实际多体验和多实践吗? 咋回头又叫我要有抽象思维啊?\n不要着急,其实两者并不矛盾,甚至相辅相成。\n所谓抽象思维,就是你理解一个东西,要先理解他的外部行为特征,把复杂的知识限制在特定的范围内。\n为什么?因为人的理解能力和沟通成本是有限的。拿我最喜欢的计算机网络举例, 如果你没有学过,你可以完全不用理会这些术语,你就先了解计算机网络能干嘛,能干嘛就是一个东西的外部特征。 而计算机网络主要功能就是使得两台远程设备之间能够通信————也就是能够交换信息,比如你说hello,他能跟你say hi。 然后你抽丝剥茧,由浅入深地学习了解,就会逐步系统地掌握计算机网络的基础知识; 如果你也学习过,那么我就没必要详细展开介绍这些概念,我可以把复杂的底层实现使用一些术语抽象起来,这样就会大大提高沟通效率。\n看出来了吗?抽象能够降低我们的沟通成本,也让我们在学习新知识的过程中,可以暂时屏蔽一些细节。\n这个能力非常重要!很多年前,我就从书籍上看到学习计算机最重要的能力是抽象能力, 但是这个表述本身也很抽象,我当时完全没法理解这句话,只是觉得这话好像很专业、 很能装X,因此把它记住了。如今很多年过去了,通过自己不断地体验和实践, 如今我尝试用我自己的语言重新把它组织了出来。\n在平常的工作学习生活中我也不断地实践这个理论。\n比如我了解一个产品或者行业,我就先了解它对外提供的核心功能,我不需要上来就要弄懂他怎样实现的, 我完全可以基于它对外展现的能力做进一步的思考或者学习。\n再比如我跟别人沟通问题时,我会有意识地留意我们当前讨论的抽象层级,当外层的内容不清晰时,讨论内层就没有意义。 当相关人员缺少对应的抽象层级的背景知识时,我会让大家优先去学习补充对应知识再讨论。\n到这里,相信已经解答了前面的疑问,但是我的标题还有另外一半——领域知识,为什么把它和抽象放在了一起呢?\n原因也很简单:抽象屏蔽掉的细节,恰恰就是对应的领域知识;又或者说,你必须具备优秀的领域知识,才能做到优秀的抽象。\n所以两者都是紧密相连的,相信有过实际业务编程经验的人都能理解,你如果不懂业务(领域知识),你就没法设计出一个优秀的系统(抽象)。 如果Vibe Coding 时代真的彻底实现技术平权,那么业务能力(领域知识)的差距将会无限放大。\n可验证和验证效率\r#\r如果 Vibe Coding 时代下,Agent 是你的员工,你可以同时拥有大量的下属,那么有什么问题是不变的?\n我想就是:验证。 如何保证把工作交给了其他人,其他人的输出符合我们的预期, 我们又应该如何提高验证效率,如何在问题出现后高效调整?\n这些问题并没有因为 Vide Coding 的出现而消失,反而变得更加重要。那我们应该如何做呢?\n自动化测试。坦白讲,有了 Vibe Coding 的辅助,编写测试的效率大大提升,我甚至觉得它很好地解决了软件开发人员没时间写测试的问题。 形式化验证。其实编译器就是个很好的例子,为什么coding领域AI内容的质量更有保证,因为它至少是通过编译器的验证的。 (这就是我为啥如今又想重新拾起自动机和形式化语言的原因) 领域知识验证。有时候 Vibe Coding怎么弄也都弄不对,我们还是要回到官方信息源、官方源代码去寻找答案的, 同时这个过程也需要我们拥有大量的领域知识。(你看,这不就闭环上了吗) 总的来说,我们最终还是要提高自动化水平的,就算退让到使用领域知识验证,目标也是为下一次的自动化验证做改进。\nKISS(Keep It Stupid Simple)\r#\r为什么把它放到最后,因为我觉得这是终极哲学——我们都需要保持简单,简单秒杀一切复杂。\n就算是 Vibe Coding 时代,同样的工具,同样的场景,有人实现就很复杂,但有人实现就很简单。\n控制成本本质也是一种保持简单。\n结语\r#\r啰啰嗦嗦终于走到了最后,我把我核心的思想基本都总结了:\n保持身心健康 接触高质量信息 在体验和实践中学习 抽象思维和领域知识 可验证和验证效率 保持简单 以上内容就构成了我现在的内核,往后就看自己如何知行合一了。\n本文完稿于2026年除夕夜,最后在这里祝自己和家人身心健康、做大做强!\n","date":"2026-02-13","externalUrl":null,"permalink":"/posts/living_under_vibe_coding/","section":"文章","summary":"前言\r#\r由于大模型的出现,软件开发产生了革命性的变化,当前prompt+Agent的使用大大提升了开发效率,欢迎来到 Vibe Coding 时代!\n","title":"Vibe Coding时代我们应该做些什么","type":"posts"},{"content":"\r前言\r#\r在重启博客之际,回顾自己7年前的老博客,不由心生感慨:“撒老师,我没白活!”\n在这7年间,软件开发的技术迭代日新月异,如今我们已经步进了Vibe Coding的时代, 大模型重塑了软件开发的范式。这一变化让很多东西变得没有意义,也让很多东西变得更有意义。\n我标题的“漫谈”是广度上的“漫谈”,也就是把我曾经写的博客都谈谈,而并非组织形式上的随便谈谈的“漫谈”。\n因此,我先说一下我的组织形式。回顾我的博客,我大致将博文分为了以下几类:编程武器、web架构、爬虫 这三大类。\n每一类,我都会解构一些重要的知识点,重新用新时代的方式表达一次;同时,对于一些淘汰落后的内容,我也会给出新时代下的替代。简单的内容就直接写在这篇博客了,复杂的内容再考虑另开博客。话不多说,我们开始吧。\n编程武器类\r#\rgit怎样使用、软件的集锦、Linux的命令、个人域名的邮箱\n关于git\r#\r版本控制在程序开发甚至内容管理上面至关重要,git 就是其中的佼佼者,在 vibe coding的时代,git 依然这么重要,甚至更上一层楼。\n毕竟当AI 生成搞坏你的代码的时候,如何恢复特别重要。而 git 能够很好的解决这个问题。\n首先,在大模型时代,我认为学习一个知识,学习宏观层面的设计尤为重要,至于细枝末节,问大模型+manual勘查即可。\n因此,我们先总结下git的宏观设计:\n逻辑区域的划分和状态转移\nstateDiagram-v2 [*] --\u003e RemoteRepo: 初始化 state \"Remote Repository(远端仓库)\" as RemoteRepo state \"Working Directory(工作区)\" as WorkSpace state \"Staging Area(暂存区)\" as Stage state \"Local Repository(本地仓库)\" as LocalRepo state \"Stash Stack(储藏栈)\" as Stash RemoteRepo --\u003e WorkSpace: git clone (克隆仓库) WorkSpace --\u003e Stage: git add (添加到暂存区) Stage --\u003e WorkSpace: git reset HEAD (撤销暂存) Stage --\u003e LocalRepo: git commit (提交) LocalRepo --\u003e WorkSpace: git checkout (检出/切换分支) LocalRepo --\u003e WorkSpace: git checkout -- file (丢弃修改) LocalRepo --\u003e RemoteRepo: git push (推送更新) RemoteRepo --\u003e LocalRepo: git fetch (获取更新) RemoteRepo --\u003e WorkSpace: git pull (拉取并合并) %% Git Stash 相关 WorkSpace --\u003e Stash: git stash (储藏当前修改) Stash --\u003e WorkSpace: git stash pop (恢复并删除储藏) %% Git Revert 相关 LocalRepo --\u003e LocalRepo: git revert (撤销提交并生成新提交) git reset HEAD 中的 HEAD 是提交记录的引用,在此不再赘述,可通过AI检索更多引用的方式\n本地仓库管理\n以下是关于 Git 本地仓库管理中常用指令的说明表格:\n指令 核心作用 命令参考 Branch 创建独立的开发线路,实现并行开发。 git branch (查看), git checkout -b (创建并切换) Merge 将分支历史合并,保留完整提交记录(会有分叉)。 git merge \u0026lt;branch-name\u0026gt; Rebase 变基,将提交移植到目标分支顶端,使历史线性化。 git rebase \u0026lt;branch-name\u0026gt; Cherry-pick 拣选,将指定的某个提交单独应用到当前分支。 git cherry-pick \u0026lt;commit-id\u0026gt; Reflog 引用日志,记录 HEAD 移动历史,用于找回误删数据。 git reflog Tag 标记特定的发布版本节点(如 v1.0),通常不随代码变动。 git tag \u0026lt;name\u0026gt;, git push origin \u0026lt;tag\u0026gt; Remote 管理远程仓库的连接别名(如 origin),实现本地与远端交互。 git remote -v (查看), git remote add (添加) 详细的用法不再赘述,使用AI问答结合实际项目多尝试即可体会其中的用处\n其他\n还有以下这些常用内容,仅作索引,详情可使用 大模型检索+manual勘误:\n在提交历史抹除指定内容(比如误提交了账号密码,需要在历史中抹除掉) 配置git(用户名、邮箱、alias、proxy、统一换行符、LFS等) git hook,Hook 就是可以在git的一些操作前后定义自己的逻辑,如:提交前自动格式化、合并时设置门禁检查等 log 查看历史;diff 对比不同commit的差异;blame 找到指定代码是谁最后改的;merge的冲突解决;以上这些都使用GUI的工具即可,VSCode 和 IDEA全家桶都有较好的支持 软件的集锦\r#\r这个没啥好说的,典型的大模型秒杀场景,现在直接根据使用场景问大模型即可,免费、开源、付费的都有\n结合我们的思路,在Agentic时代,包管理工具也有广阔的空间,Windows现在也有 choco 了\n不过话说回来,虽然包管理好用,但是有些时候我们还是需要自己编译的,所以掌握开源软件的编译也很重要\nLinux的命令\r#\r这个也是大模型秒杀的场景,最佳实践:问大模型+manual勘误+自己多尝试\n个人域名的邮箱\r#\r这个现在也不用自己搞这么麻烦了,找云厂商注册域名,备案域名,绑定一些三方邮箱即可\n云厂商是个很好的练兵场,花小钱可以学习和体验各种技术,新人还是有必要的,一定要花这笔费用\n锐评\r#\r一定要学会用命令行,多敲敲git的命令,从根本上掌握的git的设计 最高效的方式就是命令行+GUI结合。命令行适合自动化,Agentic 时代更加重要;GUI适合人工介入,对人为操作更友好 在大模型时代,我认为学习一个知识,学习宏观层面的设计尤为重要,至于细枝末节,问大模型+manual勘误即可 web架构\r#\rJava并发编程入门、Ngnix+Tomcat+MySQL项目部署、Linux-IO多路复用-SELECT/POLL/EPOLL\n高性能\r#\r首先IO多路复用时至今日,仍然是重要的技术,批量+主动提醒是其核心的优势。 另外这些函数都是系统调用,也就是说如果你使用的语言比较高级,你得查查对应的IO API的底层是什么模式。 并发编程旧博文理论太多了。用当前的眼光看:Java并发编程去学习Akka,学习什么是 Actor 模式; 去学习并发编程包的API,多使用现代的并发抽象源语 另外Golang的CSP 模式也值得学习,那句 不要通过共享内存来通信,而要通过通信来共享内存颇具哲学意味。 部署模式\r#\r独立Tomcat容器早已淘汰,拥抱Spring Boot吧 Nginx基于IO多路复用,依旧是Web的核心组件 裸机部署已过时,现在是成熟的容器化时代,docker+k8s是主流。伴随k8s生态的还有:CI/CD(持续集成,持续发布)、日志监控(ELK+grafana)、告警(Prometheus)、自动扩缩容、健康检查 关于docker和k8s又是长篇大论,就不在这里展开了,学习的时候,记得遵循先学习宏观层面的设计,至于细枝末节,问大模型+manual勘误即可 锐评\r#\r多实践,多尝试,拒绝只看理论。AI大模型时代,生成代码触手可及,带着自己的想法在vibe coding 中学习,比干巴巴的理论有趣多了呢 架构变化的核心思想还是KISS(Keep It Stupid Simple),人能理解的内容是有限的,所以我们要自动化、可重复、可观测、可追踪 爬虫\r#\r爬虫实践\u0026ndash;淘数据网站的数据爬取和存储、scrapy+selenium+headless-chrome\nHTTP协议\r#\r爬虫本质上就是构造对应的网络请求去获取对应的网络相应内容。至于如何构造,那就是对应协议的细节了,因此HTTP协议的设计是需要掌握的 HTTP协议关键概念:URL的组成和含义、请求方法、HTTP版本、请求HEADER、请求体、响应码、响应HEADER、响应体 相关知识有:REST API设计、常用HEADER及其的含义 HTTP版本有1.0、1.1、2.0、3.0,相关细节查对应RFC即可 抽象的概念特别重要,编程大多数的时候都是先有抽象的设计,然后再编码来建模,如果你不懂抽象的概念,也就不会理解相应的API设计 你问协议为啥是这样设计的?所谓协议就是彼此协商好的通信规则。特别是浏览器,就个是按web规范实现的客户端,所以当你按照web规范实现接口时,浏览器就能进行解析和渲染。 web前端\r#\r毕竟很多内容都是通过web浏览器承载的,所以学习一些浏览器知识也很有帮助的。 比如:cookie / localstorage / session / xpath / css selector / DOM / HTML 浏览器自动化目前使用 playwright,API简洁且有方便的等待机制,比 selenium 写起来舒服多了 scrapy\r#\r这个也不深入讲了,涉及到比较多Python的知识,核心流程就是:请求、响应、数据处理、入库 锐评\r#\r感慨时光易逝呀,以前的淘数据网站现在都已经404了 反爬和反反爬内容也很多,也不再赘述,给点关键词吧:浏览器自动化、JS加解密、cloudfare金盾等等 爬虫还是挺有意思的,能够激发新人学习的兴趣,配上浏览器自动化,享受vibe coding吧 结语\r#\r在这里,旧博客的复盘就结束了。\n很显然,内容不多,也比较简单,细节的内容大模型基本都能够很好地替代。\n不过在这个过程中,还是有几个新的体会值得参考的:\n在大模型时代,我认为学习一个知识,学习宏观层面的设计尤为重要,至于细枝末节,问大模型+manual勘误即可 抽象的概念特别重要,编程大多数的时候都是现有抽象的设计,然后再编码来建模,如果你不懂抽象的概念,也就不会理解相应的API设计 AI大模型时代,生成代码触手可及,带着自己的想法在vibe coding 中学习,比干巴巴的理论有趣多了呢 KISS(Keep It Stupid Simple),人能理解的内容是有限的,所以我们要自动化、可重复、可观测、可追踪 共勉\n","date":"2026-01-25","externalUrl":null,"permalink":"/posts/talk_about_my_old_blog/","section":"文章","summary":"前言\r#\r在重启博客之际,回顾自己7年前的老博客,不由心生感慨:“撒老师,我没白活!”\n","title":"漫谈博客来时路","type":"posts"},{"content":"\r为什么一直没有更新博客\r#\r翻了翻自己的博客记录,最新一篇博客是2018年11月,距离现在已经7年有余了。18年那会才刚毕业,也就是说毕业这么多年,博客从来没有更新过。\n为什么呢?其实最直接的原因就是工作繁忙。经常都要为了思考业务、实现业务挑灯夜战,闲暇下来也是身心疲惫,自然是没有什么心思再经营博客。\n其实呢,时间就像海绵里的水,挤一挤其实还是有的。没有更新的另一个原因是,选题没有什么规划。 换句话说,也就是没啥\u0026quot;KPI\u0026quot;和\u0026quot;DDL\u0026quot;,那么平时也就不会想起来还有这档事情需要完成,自然就无限期搁置了,毕竟DDL才是第一 生产力嘛 (手动狗头)\n为什么现在要更新博客\r#\r从毕业的青涩,到现在的略有经验,摸滚带爬这么了6年,中间gap了一年,让我重新思考了很多问题。\n第一,表达是一种能力。能表达出来的知识,才是自己的知识;有总结的经验,才能沉淀为个人的能力。 表达不是简单地记流水账,是需要解构自己掌握的内容,然后重新组织的。当前大模型的记忆能力吊打所有人, 单纯的记录大模型做得比任何人都好,类似cheat sheet 的功能已经被大模型替代了。 当然,我这里并非否认cheat sheet的价值,第一手的cheat sheet 价值依旧, 是我们勘误和查询的标杆,但是自己总结和二次记录的,就是被大模型吊打了。 因此,只有自己去解构掌握的内容,然后按自己的方式重新组织表达,才能沉淀为自己\u0026quot;索引\u0026quot;,更好地引导大模型为自己服务。\n第二,沟通是需要成本的。毕业后一直在一个团队,大家相处久了,知根知底,自己的能力和业绩团队都很清楚,我无须太多其他的东西来证明自己。 但是,人不能一直拘泥于一个小圈子,应该去拥抱更广阔的舞台。那么问题来了,当你面对一个陌生的人,他可能是你的面试官, 可能是你的客户,可能是你的合伙人,你如何让别人了解自己?从毕业后一直工作的团队辞职后,我就面临这个问题,向别人介绍自己很不容易。 你曾经的业绩,或因为商业机密,或因为行业壁垒,很难沟通到同频,普通只能从通用的领域和通用的知识切入,隔靴搔痒。因此,我打算认真编写一下自己的简历,并且一直迭代下去。\n我要表达什么?\r#\r如果你认真看了以上的内容,你就不难发现,我要表达的有以下三项内容:\n我的简历 我的解构的经验和总结 我的学习与思考 我的简历\r#\r这个这里不赘述了,详细信息见简历\n我的解构经验和总结\r#\r这里会先出一篇《漫谈我青涩的博文》,以7年后的沉淀,去点评一下自己的来时路,无论如何都是曾经的自己。\n然后我会在熟悉的领域切入,输出一些内容,具体选题哪些,后面会有专题详细说明,到时候我再将连接贴在这里。\n我的学习和思考\r#\r还记得换工作时,面试官问我,如何看待AI对软件开发人员的影响?我的回答的其中一个点是:保持思考,持续学习,充分利用工具提升生产力。 时代总是滚滚向前的,新知识对于任何人都是空白,我们需要的是活到老,学到老。\n你问我为啥这么有冲劲?倒也不是为了解决什么35岁的中年危机,只是不愿意成为自己讨厌的人。\n该来的,要来的,就让它来,没有什么大不了的。但是,我还是很讨厌那些年课堂上倚老卖老、脱离时代、只会吹牛的老师的。\n所以,保持学习和思考吧,这些学习和思考或许很零碎,但是终究也会成为我的解构经验和总结的。\n结语\r#\r在重启博客前,我翻了翻一些好友的博客,大多都已经断更了。其实都能理解,毕业后,大家工作都很忙,如果还成家了,家庭事务也很繁重, 写博客这种没啥收益的事情大抵都是被遗弃的。\n正是如此,能够坚持下去也显得难得可贵,希望这次自己能够挤一挤海绵里的水,努力把自己定下来的KPI完成了吧。\n","date":"2025-11-28","externalUrl":null,"permalink":"/posts/restart_blog/","section":"文章","summary":"为什么一直没有更新博客\r#\r翻了翻自己的博客记录,最新一篇博客是2018年11月,距离现在已经7年有余了。18年那会才刚毕业,也就是说毕业这么多年,博客从来没有更新过。\n","title":"重启博客杂谈","type":"posts"},{"content":" 个人信息 技术栈 履历 荣誉 项目实践 个人信息 基础信息 更多信息 现状 我的邮箱📧 lotgit150@gmail.com 开发语言 Python 主力 Java 熟悉 Golang 了解 Rust 学习中 PHP 已废弃 擅长领域 后端开发 数据工程 数据分析 人生信条互利互惠,合作共赢 / Help each other, Win together 技术理念技术深入(Technology) / 业务精通(Business) / 强力执行(Execution) / 大胆突破(Innovation) 基础能力扎实的操作系统、体系结构、计算机网络、数据结构和算法基础 管理理念懂管理,才是一名优秀的执行者;懂业务,才是一名出色的技术人员 AI 辅助研发热衷研究一切AI技术,同时也保持对AI的深度思考日常使用:Claude、Hermes | GLM Coding Plan、Deepseek V4 持续探索: SSD、Skill、MCP、Memory 技术关注点关注 Rust+Python 的未来,相信 Python 的高效率和 Rust 的高性能是最好的组合 技术栈 通用技术 后端开发 数据分析 开发工具Linux、GitVSCode、Claude Code、 网络精通 HTTP/HTTPS 协议熟悉密码学概念与实践 容器化Docker 容器化部署K8s 编排管理 认证权限TOKEN / JWT 认证RBAC / ABAC 权限模型 中间件Kafka 消息队列Flink 流批一体 可观测性Grafana 全栈套件日志 + 指标 + 追踪 数据库PostgreSQLMySQL 关系型数据库 Web框架FastAPI 异步框架 Django 同步但生态成熟 其他框架SQLAlchemy ORM框架 Pydantic 数据模型和校验 API文档OpenAPI 规范 Swagger / ReDoc 实现 任务调度Celery / APScheduler 数据处理Polars 首选 Pandas 备选和兼容 可视化Plotly 绘图 Marimo 交互式笔记本 机器学习西瓜书基础Scikit-learn/LightGBM/XGBoost 数据存储CSV 简单通用 Parquet 列式存储 履历 2014.09 \u0026ndash; 2018.06 深圳大学 / 计算机科学与技术 / 本科 2018.08 \u0026ndash; 2024.02 深圳用图科技有限公司 / 后端开发工程师、数据挖掘工程师、产品经理(兼任) 2024.02 \u0026ndash; 2025.04 GAP 2025.04 \u0026mdash; 至今 某厂 / 数据分析师 证书和荣誉 CET-4 2018 \u0026mdash; 最佳新人 2020 / 2021 \u0026mdash; 优秀员工 2025 \u0026mdash; 优秀新员工 项目实践 内部管理系统 资讯与快讯 云迁移与优化 资产统计系统 交易策略系统 Laravel MySQL MongoDB RBAC K8S CI/CD 基于 Laravel + MySQL + MongoDB 开发,Gitlab CI/CD 构建发布,K8S 部署 功能模块涵盖 RBAC 权限管理、用户管理、币种/平台/交易对管理、资讯快讯管理及动态配置管理 提出并落地基于 MongoDB 的模板化开发模式,大幅提升信息类模块的开发效率 构建管理后台公共组件库,完善单元测试和接口自动化测试并在组内推广,显著提升开发效率与代码质量 Scrapy tf-idf ElasticSearch MongoDB K8S 使用 Scrapy 爬取快讯与资讯,基于 tf-idf 相似度算法去重,MongoDB/MySQL 入库并同步 ElasticSearch 支持搜索 编写推送转发中心,对接推送系统实现内容自动分发 支持资讯投稿全流程:提交、审核、发布 提供 RESTFUL API 供应用层访问 Golang ELK 性能优化 云迁移 参与两次公司云迁移,负责后端服务与数据库迁移的沟通和执行 编写后端公共库,完善单元测试体系,提升整体开发效率 利用云压测工具进行全链路压测,输出性能报告并针对性优化:短连接改长连接、增加接口缓存、减少轮询 重构后台任务系统,模板化任务定义,自动扫描目录启动任务 建立 ELK 日志体系,支持日志检索和告警 使用 Golang 重构系统核心接口服务,将系统接口负载从 180+ 核降至 70+ 核 Python Redis Kafka InfluxDB Vegeta 基于 Python/Redis/Kafka/InfluxDB 开发,pytest 单元测试,Gitlab CI/CD 构建,K8S 部署 解决原有统计维度少、收益汇总不准确的问题,支持每日盈亏、历史盈亏、收益日历和收益走势 使用 Vegeta(Golang 压测工具)进行压力测试,输出系统性能报告 获得公司项目奖金 Python pytest K8S CI/CD 团队管理 自研 Python 回测框架和底层策略系统,pytest 单元测试,Gitlab CI/CD 构建,K8S 部署 基于生产者-消费者模式,支持水平无限扩展消费者以提高回测效率 组织产品经理完成产品原型设计 组织开发人员,完成系统架构设计和技术对接 构建测试团队,推动自动化测试落地 推进项目开发全流程,完成上线和后续维护 最后更新 · 2026 年 7 月 ","date":"2025-11-20","externalUrl":null,"permalink":"/page/resume/","section":"","summary":" 个人信息 技术栈 履历 荣誉 项目实践 个人信息 基础信息 更多信息 现状 我的邮箱📧 lotgit150@gmail.com 开发语言 Python 主力 Java 熟悉 Golang 了解 Rust 学习中 PHP 已废弃 擅长领域 后端开发 数据工程 数据分析 人生信条互利互惠,合作共赢 / Help each other, Win together 技术理念技术深入(Technology) / 业务精通(Business) / 强力执行(Execution) / 大胆突破(Innovation) 基础能力扎实的操作系统、体系结构、计算机网络、数据结构和算法基础 管理理念懂管理,才是一名优秀的执行者;懂业务,才是一名出色的技术人员 AI 辅助研发热衷研究一切AI技术,同时也保持对AI的深度思考日常使用:Claude、Hermes | GLM Coding Plan、Deepseek V4 持续探索: SSD、Skill、MCP、Memory 技术关注点关注 Rust+Python 的未来,相信 Python 的高效率和 Rust 的高性能是最好的组合 技术栈 通用技术 后端开发 数据分析 开发工具Linux、GitVSCode、Claude Code、 网络精通 HTTP/HTTPS 协议熟悉密码学概念与实践 容器化Docker 容器化部署K8s 编排管理 认证权限TOKEN / JWT 认证RBAC / ABAC 权限模型 中间件Kafka 消息队列Flink 流批一体 可观测性Grafana 全栈套件日志 + 指标 + 追踪 数据库PostgreSQLMySQL 关系型数据库 Web框架FastAPI 异步框架 Django 同步但生态成熟 其他框架SQLAlchemy ORM框架 Pydantic 数据模型和校验 API文档OpenAPI 规范 Swagger / ReDoc 实现 任务调度Celery / APScheduler 数据处理Polars 首选 Pandas 备选和兼容 可视化Plotly 绘图 Marimo 交互式笔记本 机器学习西瓜书基础Scikit-learn/LightGBM/XGBoost 数据存储CSV 简单通用 Parquet 列式存储 履历 2014.09 – 2018.06 深圳大学 / 计算机科学与技术 / 本科 2018.08 – 2024.02 深圳用图科技有限公司 / 后端开发工程师、数据挖掘工程师、产品经理(兼任) 2024.02 – 2025.04 GAP 2025.04 — 至今 某厂 / 数据分析师 证书和荣誉 CET-4 2018 — 最佳新人 2020 / 2021 — 优秀员工 2025 — 优秀新员工 项目实践 内部管理系统 资讯与快讯 云迁移与优化 资产统计系统 交易策略系统 Laravel MySQL MongoDB RBAC K8S CI/CD 基于 Laravel + MySQL + MongoDB 开发,Gitlab CI/CD 构建发布,K8S 部署 功能模块涵盖 RBAC 权限管理、用户管理、币种/平台/交易对管理、资讯快讯管理及动态配置管理 提出并落地基于 MongoDB 的模板化开发模式,大幅提升信息类模块的开发效率 构建管理后台公共组件库,完善单元测试和接口自动化测试并在组内推广,显著提升开发效率与代码质量 Scrapy tf-idf ElasticSearch MongoDB K8S 使用 Scrapy 爬取快讯与资讯,基于 tf-idf 相似度算法去重,MongoDB/MySQL 入库并同步 ElasticSearch 支持搜索 编写推送转发中心,对接推送系统实现内容自动分发 支持资讯投稿全流程:提交、审核、发布 提供 RESTFUL API 供应用层访问 Golang ELK 性能优化 云迁移 参与两次公司云迁移,负责后端服务与数据库迁移的沟通和执行 编写后端公共库,完善单元测试体系,提升整体开发效率 利用云压测工具进行全链路压测,输出性能报告并针对性优化:短连接改长连接、增加接口缓存、减少轮询 重构后台任务系统,模板化任务定义,自动扫描目录启动任务 建立 ELK 日志体系,支持日志检索和告警 使用 Golang 重构系统核心接口服务,将系统接口负载从 180+ 核降至 70+ 核 Python Redis Kafka InfluxDB Vegeta 基于 Python/Redis/Kafka/InfluxDB 开发,pytest 单元测试,Gitlab CI/CD 构建,K8S 部署 解决原有统计维度少、收益汇总不准确的问题,支持每日盈亏、历史盈亏、收益日历和收益走势 使用 Vegeta(Golang 压测工具)进行压力测试,输出系统性能报告 获得公司项目奖金 Python pytest K8S CI/CD 团队管理 自研 Python 回测框架和底层策略系统,pytest 单元测试,Gitlab CI/CD 构建,K8S 部署 基于生产者-消费者模式,支持水平无限扩展消费者以提高回测效率 组织产品经理完成产品原型设计 组织开发人员,完成系统架构设计和技术对接 构建测试团队,推动自动化测试落地 推进项目开发全流程,完成上线和后续维护 最后更新 · 2026 年 7 月 ","title":"简历","type":"page"},{"content":" bombcoder Go/后端/DevOps https://gobomb.github.io HoshI Java/Android/硬核游戏玩家 https://enderhoshi.github.io ","date":"2025-11-20","externalUrl":null,"permalink":"/page/link/","section":"","summary":" bombcoder Go/后端/DevOps https://gobomb.github.io HoshI Java/Android/硬核游戏玩家 https://enderhoshi.github.io ","title":"友链","type":"page"},{"content":"","date":"2026-02-13","externalUrl":null,"permalink":"/categories/","section":"分类","summary":"","title":"分类","type":"categories"},{"content":"","date":"2026-02-13","externalUrl":null,"permalink":"/categories/%E6%8A%80%E6%9C%AF%E5%8D%9A%E5%AE%A2/","section":"分类","summary":"","title":"技术博客","type":"categories"},{"content":"","date":"2026-02-13","externalUrl":null,"permalink":"/posts/","section":"文章","summary":"","title":"文章","type":"posts"},{"content":"前言\r#\r历史博客已归档,关于web架构的解读和导读详情见《漫谈博客来时路》\na这篇文章其实是我很久之前总结的,因为之前的笔记都比较零碎,现在统一将他迁移到博客。学习Java并发编程强烈推荐两本书,一本是《Java并发编程实战》,另一本是《Java7 并发实战手册》。\n第一本书的作者里面有 Joshua Bloch ,这位大牛可是Java Current类库的作者之一哦,类库作者亲手写的书就足够成为你看它的理由啦。Anyway,这本书对并发的理论真的讲得很好,从竞态条件、原子性到内存逸出、乐观锁、悲观锁,内容十分全面,我觉得是每一个Java程序必读的书目,可以帮助你系统地学习Java并发的知识。\nJava并发编程实战固然好,但是对于计算机基础薄弱的人来说,很多概念还是略显苦涩。如果你是这样的人的话,不妨先读一下Java7并发编程实战手册。Cook Cook Book嘛,跟着敲代码就行了,另外书中还会介绍一下并发的场景,都是对于新手很友好的哦~\n基本概念\r#\r进程是资源分配(内存、文件描述符)的基本单位,线程是轻量级的进程,线程拥有独立的PC和栈,但是共享同一进程的资源,线程是系统调度的基本单位。其实在本质上,线程是把进程的资源分配和调度执行两个属性分割,进程仅仅拥有资源,线程共享进程资源并且成为处理机调度的基本单位,从而提高并发性。\n注意:如果是单核处理器,一般单线程的执行效率会比多线程高,因为线程的调度存在上下文切换需要额外的时间。但是线程在多核上,多线程优于单线程,因为能够充分利用多个处理单位。\n进程通信的机制\r#\r管道 共享内存 消息队列 socket 进程的状态管理\r#\r就绪、运行、挂起、阻塞、结束\n多线程编程的优缺点\r#\r优点:\n很简单地就能实现异步。 能够充分利用多个处理器。 提供交互的高响应。 缺点:\n安全性。竞态条件:当多个线程访问共享资源时,如果没有同步,会造成结果不正确。 活跃性。实现得不好可能会出现饥饿、死锁、活锁等情况。 性能。主要是线程调度会带来上下文切换的开销。 线程安全\r#\r本质是管理共享(可以被多个线程访问)、可变(在其生命周期内会发生改变)的状态。\n线程安全的类:在任意调度、交替执行且无同步和协调的情况下,类的行为能保持正确。\n原子操作:要么不执行,要么执行完,操作过程中不会被调度。\n组合操作。\n可重入锁:同一个线程申请锁时,计数器加一,可以进入临界区。\n锁:sychronized Java内部锁。\n共享对象\r#\r可见性:任意一个线程的修改都能作用于其他线程,不会出现过期的数据。也就是说当A线程修改了某一共享变量的值时,B线程总能读到最新的值。\n加锁可以保证可见性和原子性。\nvolatile关键字:保证修饰的变量是可见的。一般满足以下几种情况可以使用:\n除了可见性外,没有其他原因需要加锁。 写入变量事不依赖之前的状态 。 不需要和其他变量参与不变性约束。 发布和逸出 发布:使得一个对象能被当前范围外的代码访问。 逸出:一个对象尚未准备好时就将它发布。\n比如匿名内部类this指针逸出,public方法逸出private域\n线程封闭\n使用一个线程 栈中的内容 使用ThreadLocal 不可变的对象永远是安全的。\n线程概念\r#\r1.创建线程、运行线程、输出线程的相关信息。 2.线程和线程组、守护线程、线程的异常处理器 3.线程睡眠、线程让步、线程等待、线程中断以及线程中断的判断和处理。\n编程案例\r#\r一个仓库,设置初始化容量。创建入库线程和出库线程,并发访问仓库容量。创建显示线程,实时输出仓库信息。 两个屏幕、两个售票处的电影院,每个电影院的票数独立,一个电影院的票不能用于两外一个电影院。 一个博物馆,设置参观人数的上限,当有人参观的时候,不能进行打扫,可以继续进来参观。当到达上限时,不能进入。当有人打扫时,不能有人进来参观。 一条东西向的单向马路,如果有向东的车辆行驶时,向西的车辆不能行驶,但向东的车辆可以行驶,反而亦然。 哲学家进餐问题。 打印机打印,同时只能有一个线程在打印。(多个打印机的情况) 视频会议,等待所有人到场后再开会。(重新准备,回退) 三个线程、读取三个文件夹及其子文件夹中的文件,查找24小时内被修改过的文件后缀为.txt的文件。 附加属性\r#\r公平性。等待越久的线程,优先被调度。 后记\r#\r先占着个坑啦后面如果使用较多的方面再慢慢补充\n","date":"2018-11-03","externalUrl":null,"permalink":"/posts/concurrent/","section":"文章","summary":"前言\r#\r历史博客已归档,关于web架构的解读和导读详情见《漫谈博客来时路》\na这篇文章其实是我很久之前总结的,因为之前的笔记都比较零碎,现在统一将他迁移到博客。学习Java并发编程强烈推荐两本书,一本是《Java并发编程实战》,另一本是《Java7 并发实战手册》。\n第一本书的作者里面有 Joshua Bloch ,这位大牛可是Java Current类库的作者之一哦,类库作者亲手写的书就足够成为你看它的理由啦。Anyway,这本书对并发的理论真的讲得很好,从竞态条件、原子性到内存逸出、乐观锁、悲观锁,内容十分全面,我觉得是每一个Java程序必读的书目,可以帮助你系统地学习Java并发的知识。\nJava并发编程实战固然好,但是对于计算机基础薄弱的人来说,很多概念还是略显苦涩。如果你是这样的人的话,不妨先读一下Java7并发编程实战手册。Cook Cook Book嘛,跟着敲代码就行了,另外书中还会介绍一下并发的场景,都是对于新手很友好的哦~\n","title":"Java并发编程入门","type":"posts"},{"content":"","date":"2018-09-02","externalUrl":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"本文翻译自DEVELPERS AREA的LINUX – IO MULTIPLEXING – SELECT VS POLL VS EPOLL\n前言\r#\r历史博客已归档,关于web架构的解读和导读详情见《漫谈博客来时路》\n众所周知,在linux的世界里,一切皆文件。每一个进程都拥有一张文件描述符的表,指向文件、socket、硬件还有一些操作系统对象。\n典型的拥有很多IO源的系统都会有一个初始化的阶段,然后进入待机模式——等待客户端请求并且响应。\n最简单的解决方案就是为每一个客户端创建一个线程(或者进程),一直阻塞直到请求发送或者写入了响应。这种模式在客户端数量很小的时候可以工作,但是我们想要扩展到成千上万的客户端,为每个客户端创建线程(或者进程)是一个很糟糕的主意。\nIO多路复用\r#\r问题的解决方案是使用内核机制去轮询一系列的文件描述符。在linux系统下,主要有以下三种选择:\nselect(2) poll(2) epoll 以上三种方法的思想都是一致的,新建一系列的文件描述符,告诉内核你对每一个描述符的操作,然后使用线程去阻塞一个函数调用直到至少有一个文件描述符请求的操作是可用的。\nSelect系统调用\r#\rselect()系统调用提供了一种实现同步IO多路复用的方法。\nint select(int nfds, fd_set* readfs, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) 一个对select()的调用会阻塞直到指定的文件描述符已经准备进行IO了,或者指定的超时时间到了。\n监控的文件集合分成三类:\nreadfds文件描述符集合是设置为监控数据是否可读的。 writefds文件描述符集合是设置为监控写入数据是否完成而没有阻塞。 exceptsfds设置为监控是否有异常发生或者带外(out-of-band)数据可用(一般只用于sockets)。 监控的集合可以为NULL,这种情况下select不会监控对应事件。\n在一个成功的返回,有且只有已经准备好IO的对象会被添加至对应集合.\n样例:\n#include \u0026lt;stdio.h\u0026gt; #include \u0026lt;sys/types.h\u0026gt; #include \u0026lt;sys/socket.h\u0026gt; #include \u0026lt;netinet/in.h\u0026gt; #include \u0026lt;wait.h\u0026gt; #include \u0026lt;signal.h\u0026gt; #include \u0026lt;errno.h\u0026gt; #include \u0026lt;sys/select.h\u0026gt; #include \u0026lt;sys/time.h\u0026gt; #include \u0026lt;unistd.h\u0026gt; #define MAXBUF 256 void child_process(void) { sleep(2); char msg[MAXBUF]; struct sockaddr_in addr = {0}; int n, sockfd,num=1; srandom(getpid()); /* Create socket and connect to server */ sockfd = socket(AF_INET, SOCK_STREAM, 0); addr.sin_family = AF_INET; addr.sin_port = htons(2000); addr.sin_addr.s_addr = inet_addr(\u0026#34;127.0.0.1\u0026#34;); connect(sockfd, (struct sockaddr*)\u0026amp;addr, sizeof(addr)); printf(\u0026#34;child {%d} connected \\n\u0026#34;, getpid()); while(1){ int sl = (random() % 10 ) + 1; num++; sleep(sl); sprintf (msg, \u0026#34;Test message %d from client %d\u0026#34;, num, getpid()); n = write(sockfd, msg, strlen(msg));\t/* Send message */ } } int main() { char buffer[MAXBUF]; int fds[5]; struct sockaddr_in addr; struct sockaddr_in client; int addrlen, n,i,max=0;; int sockfd, commfd; fd_set rset; for(i=0;i\u0026lt;5;i++) { if(fork() == 0) { child_process(); exit(0); } } sockfd = socket(AF_INET, SOCK_STREAM, 0); memset(\u0026amp;addr, 0, sizeof (addr)); addr.sin_family = AF_INET; addr.sin_port = htons(2000); addr.sin_addr.s_addr = INADDR_ANY; bind(sockfd,(struct sockaddr*)\u0026amp;addr ,sizeof(addr)); listen (sockfd, 5); for (i=0;i\u0026lt;5;i++) { memset(\u0026amp;client, 0, sizeof (client)); addrlen = sizeof(client); fds[i] = accept(sockfd,(struct sockaddr*)\u0026amp;client, \u0026amp;addrlen); if(fds[i] \u0026gt; max) max = fds[i]; } while(1){ FD_ZERO(\u0026amp;rset); for (i = 0; i\u0026lt; 5; i++ ) { FD_SET(fds[i],\u0026amp;rset); } puts(\u0026#34;round again\u0026#34;); select(max+1, \u0026amp;rset, NULL, NULL, NULL); for(i=0;i\u0026lt;5;i++) { if (FD_ISSET(fds[i], \u0026amp;rset)){ memset(buffer,0,MAXBUF); read(fds[i], buffer, MAXBUF); puts(buffer); } }\t} return 0; } 开始的时候,我们创建了5个子进程,每个进程都连接了服务器并且向其发送消息。服务器使用accept(2)去为每个不同客户端创建不同的文件描述符。select(2)的第一个参数应该是在三个集合中最多文件描述符集合的文件描述符数量,增加1去检测最大的fd数量。\n主循环创建了一个所有文件描述符的集合,调用select然后检查哪个文件描述符已经准备被读取了。为了简单起见,我们不加任何的错误检测。\n返回后,select只改变那些文件描述符已经就绪的集合,因此我们需要在每一次迭代构建当前的集合。\n我们需要告诉select所有集合中文件描述符(以下简称为fd)的最高数量的原因是由于fd_set的内部实现机制。每一个fd是被一个bit声明的,因此fd_set是一个32个整型的数组(32*32=1024bit)。这个函数检测所有bit去观察是否其集合已经到达了最大值。这意味着如果我们有五个fd,但是其最高的数值是900,这个函数会检测从0到900的所有bit去找到需要监控的fd。另外select有一个POSIX实现——pselect,pselect使在等待的时候使用一个掩码。\nSelect 总结\r#\r我们需要在每一次调用之前构造每一个集合。 select函数需要检测到最高位往前的任何bit——O(n)。 我们需要遍历整个fd集合去检测指定集合是否有返回。 select的主要优势就是可移植性强,几乎所有的类unix系统都支持它。 Poll 系统调用\r#\r和select函数的低效的三个位掩码fd集合不一样,poll提供了一个单独的n个pollfd的结构体数组,函数声明如下:\nint poll (struct pollfd *fd, unsigned int nfds, int timeout()); pollfd结构体的对于不同的事件和事件的返回有不同的成员,所以我们不需要每一次都构建它。\nstruct pollfd{ int fd; short events; short revents; } 对于每一个fd构造一个pollfd的对象然后填充其要求的事件。在poll返回之后检测事件变量的值。\n用poll取改变上面的例子:\nfor (i=0;i\u0026lt;5;i++) { memset(\u0026amp;client, 0, sizeof (client)); addrlen = sizeof(client); pollfds[i].fd = accept(sockfd,(struct sockaddr*)\u0026amp;client, \u0026amp;addrlen); pollfds[i].events = POLLIN; } sleep(1); while(1){ puts(\u0026#34;round again\u0026#34;); poll(pollfds, 5, 50000); for(i=0;i\u0026lt;5;i++) { if (pollfds[i].revents \u0026amp; POLLIN){ pollfds[i].revents = 0; memset(buffer,0,MAXBUF); read(pollfds[i].fd, buffer, MAXBUF); puts(buffer); } } } 像我们用selcet做的那样,我们需要检测每一个pollfd对象去观察它的fd是否已经准备好了,但是我们不需要每一次迭代都构建集合。\nPoll vs Select\r#\rpoll()不要求用户计算fd的最高值+1。 poll()在处理大数值的fd时更高效。想象一下,当你通过select只监听一个fd,但它的数值是900的时候,你需要检测每一个集合的从0到900的每一个bit。 select的fd集合是静态的大小。 使用selcect,fd集合会在返回的时候重新构建,因此随后的调用都需要重新初始化它。而poll系统调用将输入和输出分离,允许数组在没有改变的情况下复用。 select的timeout参数在返回的时候是为定义的,可移植的代码需要重新初始化它。pselect没有这个问题。 select移植性更好,有一些类unix系统不支持poll。 Epoll系统调用\r#\r当我们使用select和poll的时候,我们在用户空间处理所有的事情,然后我们每一个调用都发送fd集合然后等待。为了增加另外的socket,我们需要增加它到集合里并且重新调用select/poll。\nepoll系统调用帮助我们创建并管理在内核里的context,我们将任务分成三步。\n使用epoll_create创建一个在内核的context。 使用epoll_ctl从内核中增加或移除fd。 使用epoll_wait等待内核中的事件。 让我们把上面的例子改为epoll实现的:\nstruct epoll_event events[5]; int epfd = epoll_create(10); ... ... for (i=0;i\u0026lt;5;i++) { static struct epoll_event ev; memset(\u0026amp;client, 0, sizeof (client)); addrlen = sizeof(client); ev.data.fd = accept(sockfd,(struct sockaddr*)\u0026amp;client, \u0026amp;addrlen); ev.events = EPOLLIN; epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, \u0026amp;ev); } while(1){ puts(\u0026#34;round again\u0026#34;); nfds = epoll_wait(epfd, events, 5, 10000); for(i=0;i\u0026lt;nfds;i++) { memset(buffer,0,MAXBUF); read(events[i].data.fd, buffer, MAXBUF); puts(buffer); } } 我们首先创建了一个context(参数会被忽略但是必须是正数)。当一个客户端连接时,我们创建一个epoll_event对象然后把它添加到context,然后死循环里我们只等待context。\nEpoll vs select/poll\r#\r我们能够在等待的时候添加或者移除fd。 epoll_wait只会返回已经就绪的对象。 epoll有更好的性能——O(1) vs O(n)。 epoll可以水平触发和边缘触发。 epoll是linux特有的所以不可移植。 ","date":"2018-09-02","externalUrl":null,"permalink":"/posts/io-multiplexing/","section":"文章","summary":"本文翻译自DEVELPERS AREA的LINUX – IO MULTIPLEXING – SELECT VS POLL VS EPOLL\n前言\r#\r历史博客已归档,关于web架构的解读和导读详情见《漫谈博客来时路》\n众所周知,在linux的世界里,一切皆文件。每一个进程都拥有一张文件描述符的表,指向文件、socket、硬件还有一些操作系统对象。\n典型的拥有很多IO源的系统都会有一个初始化的阶段,然后进入待机模式——等待客户端请求并且响应。\n最简单的解决方案就是为每一个客户端创建一个线程(或者进程),一直阻塞直到请求发送或者写入了响应。这种模式在客户端数量很小的时候可以工作,但是我们想要扩展到成千上万的客户端,为每个客户端创建线程(或者进程)是一个很糟糕的主意。\nIO多路复用\r#\r问题的解决方案是使用内核机制去轮询一系列的文件描述符。在linux系统下,主要有以下三种选择:\nselect(2) poll(2) epoll 以上三种方法的思想都是一致的,新建一系列的文件描述符,告诉内核你对每一个描述符的操作,然后使用线程去阻塞一个函数调用直到至少有一个文件描述符请求的操作是可用的。\n","title":"Linux-IO多路复用-SELECT/POLL/EPOLL","type":"posts"},{"content":"","date":"2018-09-02","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"前言\r#\r历史博客已归档,关于编程武器的解读和导读详情见《漫谈博客来时路》\n记录一些linux的常用操作以便查询,暂时先记录,以后有时间再分门别类。\nLinux常用命令\r#\r项目 内容 终端ss代理 export ALL_PROXY=socks5://127.0.0.1:1080 查看系统版本 lsb_release -a 修改系统主机名 编辑 /etc/hostname 文件 查看端口使用情况 netstat -apn 输出已安装的包 rpm -qa centos/dkkg -l ubutu 清空 ctrl+u 当前输入 crtl+l 屏幕 查看占用空间 du -h \u0026lt;file or dir\u0026gt; 解压tar tar -zxf \u0026lt;target\u0026gt; 创建软连接 ln -s \u0026lt;target\u0026gt; \u0026lt;linkname\u0026gt; 搜索mysql配置文件位置 mysql \u0026ndash;help grep my.cnf 查看远程端口打开情况 telnet \u0026lt;host\u0026gt; \u0026lt;port\u0026gt; 登录日志 /var/log/secure 邮件日志 /var/log/mail 安装dns相关命令 yum install bind-utils certbot安装 issue 查看登录情况 last 查看成功的 lastb 查看失败的 free 查看内存使用情况 df 查看系统分区 top 任务管理器 cal 查看日历 date 查看时间 passwd 修改用户密码 附录\r#\rcertbot泛子域名配置\n./certbot-auto certonly --agree-tos --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory -d host -d \u0026quot;*.host\u0026quot; certbot certonly -d *.inforsecszu.net --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory\nheadless chrome安装\n安装过程 deb版本地址 rpm版本地址\n","date":"2018-08-22","externalUrl":null,"permalink":"/posts/linux-command/","section":"文章","summary":"前言\r#\r历史博客已归档,关于编程武器的解读和导读详情见《漫谈博客来时路》\n记录一些linux的常用操作以便查询,暂时先记录,以后有时间再分门别类。\n","title":"linux常用命令速查","type":"posts"},{"content":"","date":"2018-08-01","externalUrl":null,"permalink":"/tags/cheatsheet/","section":"Tags","summary":"","title":"Cheatsheet","type":"tags"},{"content":"用户初始化\r#\r历史博客已归档,关于编程武器的解读和导读详情见《漫谈博客来时路》\ngit config \u0026ndash;global user.name \u0026lt;your name\u0026gt;\ngit config \u0026ndash;global user.password \u0026lt;your password\u0026gt;\nssh-keygen 生成秘钥\nvim ~/.ssh/authorized_keys 添加客户端秘钥\ncat ~/.ssh/id_rsa.pub 输出公钥\nsu git #切换用户 sudo chsh -s /usr/bin/git-shell 更改登录的shell脚本 初始化仓库\r#\rgit init \u0026ndash;bare 创建纯仓库(只能拉取之后修改,不能直接修改)\ngit clone \u0026lt;your url\u0026gt; 克隆已有的仓库\n仓库基本操作\r#\rgit add \u0026lt;your file\u0026gt; 添加文件到暂存区\ngit commit \u0026lt;your file\u0026gt; 提交文件\n-a 提交的时候,先将所有改变添加到暂存区\ngit rm \u0026lt;your file\u0026gt; 撤销暂存区文件\ngit mv \u0026lt;your file\u0026gt; 重命名文件\ngit status 查看状态 git log 查看提交记录\n-p 查看变化\n-number 查看最近几次\n\u0026ndash;pretty=format 格式化输出内容\n\u0026ndash;since=2.weeks 输出最近两周的记录。\nvim .gitignore 添加忽略的文件\ngit commit --amend 修改提交信息或者往上一个commit里面多加几个文件。 git checkout your file 还原已经修改的文件\n远程操作\r#\rgit remote -v 查看远程分支状态\ngit add remote \u0026lt;name\u0026gt; \u0026lt;url\u0026gt; 添加分支\ngit fectch \u0026lt;branch name\u0026gt;从远程仓库拉取更新(默认分支为origin,可以指定分支)\ngit push \u0026lt;branch name\u0026gt;向远程仓库推送(默认分支为origin,可以指定分支)\ngit remote show \u0026lt;name\u0026gt; 输出分支详细信息\ngit remote remove \u0026lt;name\u0026gt; 删除分支\ngit remote mv \u0026lt;old name\u0026gt; \u0026lt;new name\u0026gt; 重命名分支 git push \u0026lt;remote\u0026gt; \u0026ndash;delete \u0026lt;branch name\u0026gt;\n技巧\r#\rgit config \u0026ndash;global alias.co chechout\n分支\r#\rgit branch \u0026lt;your branch\u0026gt; 创建分支\ngit checkout -b \u0026lt;branch name\u0026gt; 创建并切换分支\ngit checkout \u0026lt;branch name\u0026gt; 切换分支\ngit log \u0026ndash;graph 查看分支情况\ngit merge \u0026lt;branch name\u0026gt;\ngit branch -v 查看分支详情\ngit branch \u0026ndash;merged 查看已合并的分支\ngit branch \u0026ndash;no-merged 查看未合并的分支\ngit branch -d \u0026lt;branch name\u0026gt; 删除指定分支\ngit fetch \u0026lt;remote branch name\u0026gt; 从服务器拉取指定远程分支的更新\ngit push \u0026lt;remote\u0026gt; \\\u0026lt;local branch\u0026gt; 推送指定本地分支到远程分支\ngit push \u0026ndash;force 强制覆盖服务端推送 git checkout -b \u0026lt;local branch\u0026gt; \u0026lt;remote / local branch\u0026gt; 以指定远程分支为副本,创建指定本地分支\ngit rebase \u0026lt;branch\u0026gt; 设当前分支和指定分支的共同祖先节点为A,指定分支的最新节点为B,当前分支的A节点后一个节点为C,将C到A的指针指向B。\ngit rebase \u0026ndash;onto \u0026lt;dist\u0026gt; \u0026lt;branch 1\u0026gt; \u0026lt;branch 2\u0026gt; 同理如上。\nrebase和merge的差异如下图所示: 标签\r#\rgit tag -a \u0026lt;tag name\u0026gt; -m \u0026lt;message\u0026gt;\ngit tag show \u0026lt;tag name\u0026gt; 显示tag相关信息\ngit tag -l \u0026lt;regx string\u0026gt; 过滤指定tag\ngit tag -a \u0026lt;tag name\u0026gt; \u0026lt;hash str\u0026gt; 后期打标签\ngit push \u0026lt;remote branch\u0026gt; \u0026lt;tag name\u0026gt; 推送指定tag到服务器\ngit push \u0026lt;remote branch\u0026gt; \u0026ndash;tags 推送所有tag到服务端\n储藏和清理\r#\rgit stash save 保存还没提交的暂存区信息\ngit stash list 输出所有已存储的信息\ngit stash apply \u0026lt;id\u0026gt; 应用栈顶的储存,指定id时,应用指定id的储存\ngit stash clear 清理所有储存\ngit clean 清理所有已被跟踪的文件\n代理\r#\rgit config \u0026ndash;global https.proxy http://127.0.0.1:1080\ngit config \u0026ndash;global https.proxy https://127.0.0.1:1080\ngit config \u0026ndash;global \u0026ndash;unset http.proxy git config \u0026ndash;global \u0026ndash;unset https.proxy\n删除提交记录\r#\rgit reset head~1(回退到head指针往回一个版本)\n中文支持\r#\rvim ~/.bashrc\nexport LANG=zh_CN.UTF-8\n","date":"2018-08-01","externalUrl":null,"permalink":"/posts/git/","section":"文章","summary":"用户初始化\r#\r历史博客已归档,关于编程武器的解读和导读详情见《漫谈博客来时路》\ngit config –global user.name \u003cyour name\u003e\ngit config –global user.password \u003cyour password\u003e\nssh-keygen 生成秘钥\nvim ~/.ssh/authorized_keys 添加客户端秘钥\ncat ~/.ssh/id_rsa.pub 输出公钥\nsu git #切换用户 sudo chsh -s /usr/bin/git-shell 更改登录的shell脚本","title":"git常用操作汇总","type":"posts"},{"content":"常用软件汇总\r#\r前言\r#\r历史博客已归档,关于编程武器的解读和导读详情见《漫谈博客来时路》\n因为可能经常换电脑,或者电脑重装系统,所以经常需要重装软件,记录一下使用的软件及其下载地址是很有必要的,方便日后查阅。\n以下下载链接都是win下的版本,暂时没收录其他系统。\n开发工具\r#\r软件 功能 下载地址 idea Community Java开发IDE 下载地址 JDk Java开发工具 下载地址 MySQL 数据库 下载地址 XShell 终端 下载地址 XFTP FTP客户端 下载地址 git git客户端 下载地址 vscode 编辑器 下载地址 postman 模拟HTTP请求工具 下载地址 fiddler HTTP抓包工具 下载地址 frp 反代工具 下载地址 shadowsocks 正向代理工具 下载地址 效率工具\r#\r软件 功能 下载地址 foxmail 邮件客户端 下载地址 hotkeys 改键工具 下载地址 飞鸽传书 内网传输软件 下载地址 todoist 清单软件 在线地址 日常工具\r#\r软件 功能 下载地址 chrome 浏览器 下载地址 火绒 安全软件 下载地址 clover 文件浏览器 下载地址 TIM IM软件 下载地址 微信 IM软件 下载地址 结语\r#\r此清单持续更新,收集各种好用软件。\n","date":"2018-08-01","externalUrl":null,"permalink":"/posts/software/","section":"文章","summary":"常用软件汇总\r#\r前言\r#\r历史博客已归档,关于编程武器的解读和导读详情见《漫谈博客来时路》\n因为可能经常换电脑,或者电脑重装系统,所以经常需要重装软件,记录一下使用的软件及其下载地址是很有必要的,方便日后查阅。\n以下下载链接都是win下的版本,暂时没收录其他系统。\n开发工具\r#\r软件 功能 下载地址 idea Community Java开发IDE 下载地址 JDk Java开发工具 下载地址 MySQL 数据库 下载地址 XShell 终端 下载地址 XFTP FTP客户端 下载地址 git git客户端 下载地址 vscode 编辑器 下载地址 postman 模拟HTTP请求工具 下载地址 fiddler HTTP抓包工具 下载地址 frp 反代工具 下载地址 shadowsocks 正向代理工具 下载地址 ","title":"常用软件汇总","type":"posts"},{"content":"\r前言\r#\r历史博客已归档,关于web架构的解读和导读详情见《漫谈博客来时路》\n毕业设计做了一个签到系统的服务端,架构使用的Java栈的传统架构,也是最简单的架构。这个架构主要用Nginx做反向代理和负载均衡(我的项目没有使用到这一块),Tomcat作为Servlet的容器,MySQL作为数据库。项目也比较基础,没有太多难点,但是在项目部署过程当中还是遇到了一些问题,所以想着把部署的过程及记录下来,方便自己查看。\n系统环境\r#\r操作系统:CentOS 7.4 x64\nJDK: 1.8.171\nMySQL: 5.7\nTomcat: 9.0.8\n软件安装\r#\rMySQL数据库\r#\rMySQL由于历史原因(就是Oracle之前收购MySQL,社区担心Oracle闭源MySQL数据库,所以更改了源,默认的源是MariaDB)没有办法直接使用yum命令安装,所以需要手动下载然后添加。\n$ wget https://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm 首先下载MySQL 5.7版本的包,然后添加到仓库。\n$ sudo rpm -ivh mysql57-community-release-el7-9.noarch.rpm 然后直接使用yum命令安装MySQL。\n$ sudo yum install mysql-server 安装完成后直接启动MySQL。\n$ sudo mysql -u root 如果启动失败,可以打开配置文件,开启免密登录。\n$ sudo vim /etc/my.cnf [mysqld] skip-grant-tables 进入MySQL后,就可以初始化配置了。首先可以修改密码和监听的地址。\n\u0026gt; use mysql; \u0026gt; ALTER PASSWORD PASSWORD(\u0026#39;your password\u0026#39;) \u0026gt; UPDATE user SET host=\u0026#39;ip or %\u0026#39;; \u0026gt; SET GOBAL max_connections=10000; \u0026gt; exit 注释掉前面的那一句skip-grant-tables,然后重启mysql。\n$ systemctl restart mysqld 然后启动mysql,创建名为sign_system的数据库,导入备份的数据。\n\u0026gt; CREATE DATABASE sign_system; \u0026gt; exit $ mysql -u root -p \u0026lt; backup.sql 然后数据库部分的部署基本完成。\nJava环境配置\r#\r首先下载JDK,从这个网址下载,下载完成后,解压。\n$ tar -zxvf file.tar.gz 然后配置环境变量。\n$ vim /etc/profile JAVA_HOME=\u0026#39;your dir\u0026#39;; PATH=\u0026#39;$JAVA_HOME/bin:$PATH\u0026#39; 如果默认安装有open-jdk,可以使用命令卸载了它。\n$ sudo yum remove open-jdk Tomcat安装配置\r#\r下载Tomcat。\n$ wget http://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.8/bin/apache-tomcat-9.0.8.tar.gz 解压。\n$ tar -zxvf \u0026lt;your file\u0026gt; 配置tomcat环境变量,进入tomcat目录下的bin目录,创建setenv.sh文件,输入内容。\n$ CATALINA_HOME/bin touch setenv.sh $ vim setenv.sh #! /bin/sh # add tomcat pid CATALINA_PID=\u0026#34;$CATALINA_HOME/tomcat.pid\u0026#34; JAVA_HOME=\u0026#39;your dir\u0026#39; # add catalina option CATALINA_OPTS=\u0026#34;-Djava.rmi.server.hostname=uzpeng.top -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.password.file=$CATALINA_HOME/conf/jmxremote.password -Dcom.sun.management.jmxremote.access.file=$CATALINA_HOME/conf/jmxremote.access -Dcom.sun.management.jmxremote.ssl=false\u0026#34; 配置CATALINA_HOME环境变量\n$ vim /etc/profile CATALINA_HOME=\u0026#39;your dir\u0026#39; 最后配置启动脚本。\n$ vim /lib/systemd/system/tomcat.service [Unit] Description=Apache Tomcat Web Application Container After=syslog.target network.target [Service] Type=forking PIDFile=\u0026#39;your dir\u0026#39;/tomcat.pid ExecStart=\u0026#39;your dir\u0026#39;/bin/catalina.sh start ExecReload=/bin/kill -s HUP $MAINPID ExecStop=/bin/kill -s QUIT $MAINPID PrivateTmp=true SuccessExitStatus=143 [Install] WantedBy=multi-user.target 在tomcat目录下创建tomcat.pid文件。\n$ touch tomcat.pid 上传war文件(我使用的是Xftp)到tomcat目录下的webapp,重命名为ROOT.war。然后接着编辑$CATALINA_HOME/conf/server.xml\n\u0026lt;Host name=\u0026#34;localhost\u0026#34; appBase=\u0026#34;webapps\u0026#34; unpackWARs=\u0026#34;true\u0026#34; autoDeploy=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;Context docBase=\u0026#34;ROOT.war\u0026#34; path=\u0026#34;\u0026#34;/\u0026gt; ....... \u0026lt;/Host\u0026gt; 重启tomcat\n$ sudo systemctl restart tomcat Nginx配置\r#\r首先直接使用yum命令安装nginx。\n$ sudo yum install nginx 然后打开配置文件配置。\n$ sudo vim /etc/nginx/nginx.cnf 以下是我的配置文件。\nuser nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; # Load dynamic modules. See /usr/share/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf; events { use epoll; worker_connections 1024; } http { log_format main \u0026#39;$remote_addr - $remote_user [$time_local] \u0026#34;$request\u0026#34; \u0026#39; \u0026#39;$status $body_bytes_sent \u0026#34;$http_referer\u0026#34; \u0026#39; \u0026#39;\u0026#34;$http_user_agent\u0026#34; \u0026#34;$http_x_forwarded_for\u0026#34;\u0026#39;; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. include /etc/nginx/conf.d/*.conf; proxy_set_header X-Real-IP $remote_addr; server { if ($host = \u0026lt;host\u0026gt;) return 301 https://www.$host$request_uri; } server_name \u0026lt;host\u0026gt;; } server { server_name api.host; location / { proxy_pass http://localhost:8080; keepalive_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; proxy_http_version 1.1; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_set_header X-NginX-Proxy true; # prevents 502 bad gateway error proxy_buffers 8 32k; proxy_buffer_size 64k; proxy_redirect off; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; } # managed by Certbot listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/\u0026lt;host\u0026gt;/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/\u0026lt;host\u0026gt;/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } server { server_name www.host; location / { proxy_pass http://localhost:4000; keepalive_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; proxy_http_version 1.1; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_set_header X-NginX-Proxy true; # prevents 502 bad gateway error proxy_buffers 8 32k; proxy_buffer_size 64k; proxy_redirect off; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; } # managed by Certbot listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/\u0026lt;host\u0026gt;/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/\u0026lt;host\u0026gt;/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } 安装证书\r#\r证书使用的是Let\u0026rsquo;s Encrypt的泛子域名证书,安装步骤如下:\n$ sudo yum install certbot $ sudo yum install python2-certbot-nginx $ sudo certbot --nginx $ sudo certbot certonly --agree-tos --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory -d host -d \u0026#34;*.host\u0026#34; 总结\r#\r简单地记录了一下系统部署的过程,方便以后查阅,以后如果有遇到新的问题会在本文章继续补充。\n","date":"2018-06-20","externalUrl":null,"permalink":"/posts/deploy-doc/","section":"文章","summary":"前言\r#\r历史博客已归档,关于web架构的解读和导读详情见《漫谈博客来时路》\n","title":"Ngnix+Tomcat+MySQL项目部署","type":"posts"},{"content":"","date":"2018-06-20","externalUrl":null,"permalink":"/tags/%E5%90%8E%E7%AB%AF/","section":"Tags","summary":"","title":"后端","type":"tags"},{"content":"前言\r#\r历史博客已归档,关于编程武器的解读和导读详情见《漫谈博客来时路》\n我对于邮箱理解,在很长一段时间里,都是qq邮箱、163邮箱、126邮箱等等的各种第三方邮箱。这些邮箱都是注册一下,然后从web页面登陆进去,就可以收发邮件了。后来上了计算机网络这门课,学习到了有关于邮件的一些协议比如SMTP、POP3、IMAP。但是,当时也只是知道有这几种协议,也没有去实践过,现在想起来真的觉得计算机各方面知识自己都实践得太少了。而且最近在学习java web开发,想着实现一个邮件发送验证码的功能,同时又想拥有一个自己的域名邮箱,所以就决定配置一下邮件服务器。\n技术框架\r#\r系统环境\r#\r命令行输入lsb_realse -a,查询结果如下:\nLSB Version:\t:core-4.1-amd64:core-4.1-noarch Distributor ID:\tCentOS Description:\tCentOS Linux release 7.3.1611 (Core) Release:\t7.3.1611 Codename:\tCore 软件\r#\r软件 说明 postfix 提供SMTP服务 dovecot 提供POP3、IMAP服务 协议解释\r#\r协议 说明 端口 SMTP imple Mail Transfer Protocol 主要作用是推送邮件 25 POP3 Post Office Protocol 主要作用是从服务器获取邮件 110 IMAP Internet Mail Access Protocol 主要作用是从服务器获取邮件 143 协议的工作流程大致如下: 部署实践\r#\r好的,理论就说到这里了,下面开始部署实践。\n首先,查看系统有没有带有sendmail,有就卸载了,然后安装postfix和dovecot\nrpm -qa | grep sendmail yum remove sendmail yum install postfix yum install dovecot 然后开始配置工作,首先配置postfix。\nvim /etc/postfix/main.cf 将默认配置改为以下配置:\nmyhostname = uzpeng.top mydomain = uzpeng.top myorigin = $myhostname inet_interfaces = all mydestination = $myhostname, localhost.$mydomain, localhost mynetworks_style = host relay_domains = $mydomain smtpd_sender_restrictions = permit_mynetworks permit smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, check_recipient_access hash:/etc/postfix/access, defer_unauth_destination mynetworks = 127.0.0.1/32 172.16.252.225/32 \u0026lt;your client ip\u0026gt;/32 接着配置dovecot\nvim /etc/dovecot/dovecot.conf 添加如下配置:\nlogin_trusted_networks = \u0026lt;your client ip\u0026gt; mail_location=maildir:~/Maildir 然后分别启动服务\nsystemctl start postfix systemctl start dovecot 接着防火请打开,25,110,143端口。\nDNS解析添加MX记录指向自己的服务器。\n至此,服务器配置完毕。\n默认服务器有一个名为liu的用户,下面开始客户端的配置。搜索下载foxmail,账号管理新增用户填充信息如图 点击创建就可以创建成功了。\n然后到了这里,我可以成功的接收邮件了,但是发送的话一直就是接收不到。查看了一下运行日志,提示如下错误:\nMar 3 17:30:36 uzpeng postfix/smtp[24921]: connect to mx3.qq.com[183.57.48.35]:25: Connection timed out Mar 3 17:30:36 uzpeng postfix/smtp[24921]: connect to mx3.qq.com[240e:ff:f040:28::f]:25: Network is unreachable Mar 3 17:31:06 uzpeng postfix/smtp[24921]: connect to mx2.qq.com[14.17.41.170]:25: Connection timed out Mar 3 17:31:36 uzpeng postfix/smtp[24921]: connect to mx2.qq.com[59.37.97.124]:25: Connection timed out 纳尼?网络都连不上吗?赶紧运行telnet mx3.qq.com 25测试了一下端口,结果如下,一直连不通。\n后来我本地运行telnet mx3.qq.com 25,妥妥得连接上去了,如图 所以基本断定是服务器的问题了,但是服务器的防火墙是打开了25端口的,莫非阿里云屏蔽这个端口?搜索引擎一搜,果然是这么一回事。 所以搞了一个下午,最终的结果就是阿里云不允许直接使用stmp发送邮件,必须通过第三方。\n基于第三方邮箱配置自己的域名邮箱\r#\r既然阿里云都屏蔽了端口,所以没办法咯,只能用第三方的了,那就直接用阿里家的呗。\n控制台\u0026gt;域名与万网\u0026gt;企业邮箱\u0026gt;管理,然后修改管理员账号密码,登录进去,创建一下用户,配置一下基本信息,这些都是比较傻瓜式的操作了,不再赘述。\n最后域名解析按照阿里云的提示来,主要是添加MX记录到阿里云的邮箱服务器,添加CNAME记录到阿里的stmp,pop3,imap服务器,服务器端基本就配置完成啦。\n客户端的配置和话,和自己搭建的差不多啦,就是服务器地址换一换。\n遇到的问题\r#\r登录不上去。\nMar 3 14:42:35 uzpeng dovecot: imap(liu): Error: Invalid user settings. Refer to server log for more information. Mar 3 14:42:35 uzpeng dovecot: imap-login: Login: user=\u0026lt;liu\u0026gt;, method=PLAIN, rip=218.17.207.66, lip=172.16.252.225, mpid=15181, secured, session=\u0026lt;PBHwZXxmnwDaEc9C\u0026gt; dovecat配置文件添加\nmail_location=maildir:~/Maildir 结语\r#\r弄了一个下午,总算有了自己的域名邮箱。虽然没有成功在自己服务器跑邮箱服务,但是捣鼓这么一遭,自己对邮件服务还是有了更深刻的理解。好了,最近捣鼓的内容差不多就这样了,接下来需要把时间投入到java web和春招中去了,希望自己能找到满意的工作!\n","date":"2018-03-06","externalUrl":null,"permalink":"/posts/mail/","section":"文章","summary":"前言\r#\r历史博客已归档,关于编程武器的解读和导读详情见《漫谈博客来时路》\n我对于邮箱理解,在很长一段时间里,都是qq邮箱、163邮箱、126邮箱等等的各种第三方邮箱。这些邮箱都是注册一下,然后从web页面登陆进去,就可以收发邮件了。后来上了计算机网络这门课,学习到了有关于邮件的一些协议比如SMTP、POP3、IMAP。但是,当时也只是知道有这几种协议,也没有去实践过,现在想起来真的觉得计算机各方面知识自己都实践得太少了。而且最近在学习java web开发,想着实现一个邮件发送验证码的功能,同时又想拥有一个自己的域名邮箱,所以就决定配置一下邮件服务器。\n","title":"postfix+dovecot搭建自己的邮件服务器","type":"posts"},{"content":"","date":"2018-03-06","externalUrl":null,"permalink":"/tags/%E7%BD%91%E7%BB%9C/","section":"Tags","summary":"","title":"网络","type":"tags"},{"content":"前言\r#\r历史博客已归档,关于爬虫的解读和导读详情见《漫谈博客来时路》\n久闻scrapy大名,一直想找机会了解一下这框架,在兴趣的驱动下学习了一下简单的scrapy的使用。虽然上次用java做的一个爬虫,但是后来觉得也算不上什么爬虫吧,只能算模拟http请求和http响应的处理、json的序列化和反序列化、excel文件的读取和写入而已,很多爬虫需要处理的东西也并没有处理。 但是scrapy就不一样了,作为一个成熟的爬虫框架,scrapy有以下几点优势。\n内嵌了CSS选择器和XPath解析器用于提取HTML/XML的信息。 提供了一个shell控制台用于测试css和xpath的表达式。 良好的架构设计:middleware、pipeline。 成熟的请求调度器。 备注: 以上特性来自于scrapy官方文档\nscrapy还有很多其他的特性,我在实践中比较常用的就是上面这几项。\n另外,上次爬取动态页面的时候,采取的策略是分析AJAX请求的URL然后自己构造请求。这种方法比较麻烦,需要自己去分析请求,这次我们采用selenium+headless-chrome,以浏览器自动化的方式爬取数据。\n技术框架\r#\r编程语言:python 库:scrapy、pymysql、selenium 工具:headless chrome\n框架 说明 scrapy python著名爬虫框架 pymysql python数据库访问框架 selenium 浏览器自动化框架 headless chrome 命令行运行的chrome 数据流\r#\r在研究数据流之前,我们首先看一下scrapy设计的架构。 spider创建Request对象。 request对象经过SpiderMiddleware Engine获取了spider的请求。 Engine将请求发送给scheduler调度,继续拿下一个请求。 scheduler调度请求,发送到downloadMiddleware。 根据downloadMiddleware process_request()方法的返回结果,选择继续发送给downloader或者是返回response给Engine Engine将response发送给spider middleware spider middleware将response发送给spider spider 调用注册的回调函数(默认parse()) 根据返回结果调用pipeline和继续将请求发送给Engine 制作了一个序列图如下: 项目实战\r#\r下载chrome driver,将可执行文件添加到环境变量。windows下直接下载chrome,Linux命令行下载chrome步骤如下:\ndeb版本地址:https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb rpm版本地址:https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm wget \u0026lt;下载地址\u0026gt; centos: yum install \u0026lt;安装包文件名\u0026gt; ubuntu: apt install \u0026lt;安装包文件名\u0026gt; 然后安装python需要的几个库\npip install pymysql pip install scrapy pip install selenium 下面我们爬取某二手车网站的数据,爬取思路大致如下:\n获取某二手车网站的url。 向该URL请求数据。 写一个download middleware,拦截该请求,将请求交给chrome处理。 得到渲染好的HTML文档后,编写css、xpath表达式提取数据。 编写一个item pipeline,将数据写入数据库。 项目源代码在这里,在此不过多叙述实现的细节。\n反爬虫与反反爬虫\r#\r在完成这个项目的过程中,在知乎上看到一个关于反爬虫的回答[1]总结得挺有意思的。内容涵盖了反爬虫的方方面面,我总结一下他提到内容:\n只允许指定UserAgent的请求。但是这个请求头太好伪造啦,所以这样方法应该根本不可行。 IP白名单。就是只让一部分IP访问,但是这对web应用是不现实的啦。因为web是公开的。 带有浏览器特征的AJAX请求并且前端进行特殊处理后渲染。这个时候呢就是用selenium+headless-chrome啦,浏览器驱动+浏览器自动化技术。 监控请求频率和时段,同时设置验证码。请求频率和时段的话,控制爬取的时间段和频率就可以了。传统验证码的话,可以用机器学习的方法图像识别。 传统验证码容易被图像识别,所以现在产生各种拖动滑条、点击图中的指定的字这种验证码,这种验证码用机器处理难度还是比较大的。但是呢,可以花钱请专门的“打码”团队帮忙,这个在爬虫领域还是很常见的。 在实践过程中,爬取某二手车网站的时候,直接模拟http请求是不可行的,因为会提示如下信息: 这就是典型的利用浏览器特征反爬虫的,就是不允许非浏览器的请求。解决办法就是自动化浏览器请求。\n另外我自己也简单尝试了一下图像识别验证码的。当时使用的是tessract和PIL,因为我没做二值化处理和训练模型,所以是失败了。因为不想再这方面投入更大的时间,所以就此作罢。\n最后,我在程序也实现了指定时间段和频率控制的功能。指定时间段的话也很简单,就是获取当前时间,是指定时间就运行非指定时间睡眠就可以了。控制的频率的话,加入延迟的就好。\n浏览器自动化技术\r#\r在这次爬虫的过程中,接触到了浏览器自动化技术。其实呢,这项技术本身是用来进行浏览器自动化测试的,只是很多人也用它进行爬虫。浏览器自动化是一个有趣的事情,让我们简单看看它的效果。 这是一个自动化登录的例子,哈哈哈,是不是有点酷第一次看到可以自动化还是有点小激动的\n另外,很多人都用过headless browser,不过网上很多文章都是使用PhantomJS的,而我为什么不用呢,主要是因为PhantomJS作者在2017年4月13日就宣布停止维护和更新它啦~因为在无界面浏览器领域,chrome出一个版本,所以PhantomJS的作者就停止维护了,以下是他的帖子。 原文链接 作者主要是观点是认为headless chrome比PhantomJS更加流畅和稳定,而且更节省内存。而且作者是自己一个人开发的,(大神收下我的膝盖)所以就停止维护这个项目啦。\n最后,其实浏览器自动化的话,除了使用selenium外,在这次爬虫过程中还发现了另外两个库puppeteer和nightmare。第一个是google-chrome官方团队做的,而且更新频率还挺高的挺多人维护的。这两个框架就没时间研究啦,只是了解到,记录一下。\n附录:\n浏览器自动化测试框架链接 有没有可能检测然后禁止headless-chrome呢? \u0026ldquo;结语\u0026rdquo;\r#\r实践是检验真理的唯一标准。知道是怎么样和能够做出来是两回事。作为一个工程师,还是要多多实践的。做这个项目前后用了两周的时间,经历了学习python的简单使用、阅读scrapy的官方文档、阅读selenium文档、学习css选择器和xpath表达式、实现项目、部署上线。\n另外在项目过程中也阅读了大量的英文文献,本来也想着先看看中文的书籍先的,但是感觉书籍里面太多无关紧要的内容了,所以还是选择了阅读英文文献。在此过程中阅读英文文献有以下几个优点:\n系统、全面。 带有大量demo、samples。 一手信息,更新及时。 当然缺点也有:\n阅读效率低(主要还是自己英文渣渣) 学好英文多重要啊!还是继续好好学习吧!\n","date":"2018-03-05","externalUrl":null,"permalink":"/posts/scrapy/","section":"文章","summary":"前言\r#\r历史博客已归档,关于爬虫的解读和导读详情见《漫谈博客来时路》\n久闻scrapy大名,一直想找机会了解一下这框架,在兴趣的驱动下学习了一下简单的scrapy的使用。虽然上次用java做的一个爬虫,但是后来觉得也算不上什么爬虫吧,只能算模拟http请求和http响应的处理、json的序列化和反序列化、excel文件的读取和写入而已,很多爬虫需要处理的东西也并没有处理。 但是scrapy就不一样了,作为一个成熟的爬虫框架,scrapy有以下几点优势。\n内嵌了CSS选择器和XPath解析器用于提取HTML/XML的信息。 提供了一个shell控制台用于测试css和xpath的表达式。 良好的架构设计:middleware、pipeline。 成熟的请求调度器。 备注: 以上特性来自于scrapy官方文档\nscrapy还有很多其他的特性,我在实践中比较常用的就是上面这几项。\n另外,上次爬取动态页面的时候,采取的策略是分析AJAX请求的URL然后自己构造请求。这种方法比较麻烦,需要自己去分析请求,这次我们采用selenium+headless-chrome,以浏览器自动化的方式爬取数据。\n","title":"scrapy+selenium+headless-chrome爬虫总结","type":"posts"},{"content":"","date":"2018-03-05","externalUrl":null,"permalink":"/tags/%E7%88%AC%E8%99%AB/","section":"Tags","summary":"","title":"爬虫","type":"tags"},{"content":"背景\r#\r历史博客已归档,关于爬虫的解读和导读详情见《漫谈博客来时路》\n最近女朋友需要利用tableau来做数据可视化的作业,要做数据可视化,那首先可定得要有数据。她们打算用八爪鱼等爬虫软件来抓取数据,结果没成功,原因不明。然后跟我吐槽了一下这个事情,刚好我最近也要学爬虫,本着项目驱动学习的理念,就直接选择了这个项目来练手了。\n准备工作\r#\r编程环境:\nIntelliJ IDEA 2017.1.5 Build #IU-171.4694.70, built on July 4, 2017 Licensed to weapon JRE: 1.8.0_112-release-736-b21 amd64 JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o Windows 10 10.0 编程环境如上所示,因为我Java语言比较熟悉,所以编程语言选择了Java。虽然Java做爬虫语法上面比较累赘,但是Python还没学会,就凑合着用Java写了。\n流程\r#\r我认为的爬虫的流程主要有以下几步\n确定目标:确定要抓取的网站和需要抓取的内容。 分析数据请求:利用浏览器的控制台分析网站数据请求的方式(同步请求还是异步请求)。 模拟请求:构造请求,获取数据。 分析数据:分析数据,提取有效信息。 存储数据。将数据存储到本地。 实践过程\r#\r确定目标\r#\r我要抓取的网站是淘数据的店铺监控下面的鸿星尔克、德芙、Opus三家店铺的数据,具体的数据如下图所示: 主要包含店铺的数据明细、滞销宝贝、宝贝的上新跟踪、宝贝的改名记录、钻石展位、聚划算、淘宝客。\n分析数据请求\r#\r打开chrome浏览器,F12打开控制台,点击elements审查元素,通过元素审查我们可知我们需要爬取的数据是通过Ajax异步请求的。接着点击network标签查看网络请求,点击XHR子标签查看Ajax请求,分析请求列表,找到我们需要的请求,单击查看请求详情。\nPS: 浏览器都是通过HTTP协议进行数据交互的,所以要爬虫的话,还是需要对HTTP有一定的了解。可以参考《HTTP权威指南》、《图解HTTP》等书籍。\n从图中我们可以很明显地得到包括请求和响应在内的Http的详细信息,包括请求的方法、请求的URL、请求的主机、请求的头部以及响应状态码,响应体的内容。Http的每个头部都有其含义,具体请参阅《HTTP权威指南》。在这里我们只关心和我们本次请求相关的内容,整理如下:\n字段 内容 请求方法 GET 请求URL http://taosj.com/...../....stat Cookie 主要关心auth字段 请求URL还可以进一步分析其参数组成。以图中的URL为例,http://www.taosj.com/协议和主机地址,data/shop/offer/list主机下的目录,api_name=shop_get_offer_list...stat=长长的参数列表。\n因为访问的数据是需要认证的,常见的认证方式有cookie和token(OAuth2.0)。经分析报头和尝试请求发现,网站是采用cookie认证的,而且主要的认证cookie字段是auth。\n接着分析响应体,响应体是JSON格式的字符串(关于JSON可以关注其官网)。\n模拟请求、分析数据、存储数据\r#\r经过上面的分析,我们已经知道了网站数据请求的方式、数据认证的方式、还有请求相关的信息。接下来就可以开展编码工作了。\n编码的环境前面已经介绍了,接下来介绍一下使用的开源库。\nokhttp: 负责http网络请求。* json-java: java端json实现。 gson: google官方的json解析库。 poi: apache开源的excel操作库。 项目构建工具使用gradle,构建脚本如下,源码在最后。\ngroup \u0026#39;uzpeng\u0026#39; version \u0026#39;1.0-SNAPSHOT\u0026#39; apply plugin: \u0026#39;java\u0026#39; apply plugin: \u0026#39;idea\u0026#39; sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { testCompile group: \u0026#39;junit\u0026#39;, name: \u0026#39;junit\u0026#39;, version: \u0026#39;4.12\u0026#39; compile group: \u0026#39;org.apache.poi\u0026#39;, name: \u0026#39;poi\u0026#39;, version: \u0026#39;3.17\u0026#39; compile group: \u0026#39;com.squareup.okhttp3\u0026#39;, name: \u0026#39;okhttp\u0026#39;, version: \u0026#39;3.9.1\u0026#39; compile group: \u0026#39;org.json\u0026#39;, name: \u0026#39;json\u0026#39;, version: \u0026#39;20160810\u0026#39; compile group: \u0026#39;com.google.code.gson\u0026#39;, name: \u0026#39;gson\u0026#39;, version: \u0026#39;2.8.2\u0026#39; } 首先,根据浏览器控制台的响应信息,使用GsonFormat插件生成实体类的信息。然后,对照网页显示,确认每一个字段的意义。接着,根据前一个步骤获取的Url和参数信息定义好Url和参数。接着,构造http请求,拿到数据。最后,分析数据,写入excel文件。\n我们请求的三个店铺都是不需要认证的,如果需要访问其他店铺数据的话,就需要登录。然后照着上面的分析请求的方法,取出cookie的auth字段,最后在构造请求的时候添加cookie头部信息。\n接着介绍一下代码实现,其主要包含以下四个模块:主函数、实体类、网络请求、写入文件。\n网络请求的主要函数如下:\npublic Response request(String url){ Request request = new Request.Builder() .url(url) .addHeader(\u0026#34;Cookie\u0026#34;,Cookie) .get() .build(); try { return client.newCall(request).execute(); } catch (IOException e) { e.printStackTrace(); return null; } } 请求很简单,传入我们分析得出的Url和cookie信息,构造请求,然后同步执行,返回结果。\n项目整体的数据流大致如下:\nmain函数创建RequestModel,调用RequestModel的requestXXX方法。 RequestModel调用HttpClient的request方法。 根据返回的数据调用WriteToExcel()方法。 writeProxy创建动态代理。 动态代理的方法里面调用writeIntoExcelManger的outputXXX()方法,最后写入excel。 代码实现有两点值得提一下: 一是利用jdk动态动态代理,实现AOP,将写入excel的具体内容的代码插入创建excel工作簿和写入本地磁盘之间。\nWriteInvocationHandler outputInvocationHandler = new WriteInvocationHandler(); outputInvocationHandler.setWriteIntoExcelProxy(new WriteIntoExcelManager()); IWriteIntoExcel proxy = (IWriteIntoExcel) Proxy.newProxyInstance(WriteIntoExcelManager.class.getClassLoader(), new Class[]{IWriteIntoExcel.class}, outputInvocationHandler); proxy.writeIntoExcel(file, titles, entities, flag); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { File file = (File) args[0]; String[] titles = (String[])args[1]; List\u0026lt;?\u0026gt; list = (List\u0026lt;?\u0026gt;)args[2]; int flag = (Integer) args[3]; try{ Workbook workbook = new HSSFWorkbook(); Sheet sheet = workbook.createSheet(); writeIntoExcelProxy.setSheet(sheet); Row row = sheet.createRow(0); for (int i = 0; i \u0026lt; titles.length; i++) { row.createCell(i).setCellValue(titles[i]); } switch (flag){ case WriteProxy.FLAG_OFFER_DETAIL: writeIntoExcelProxy.offerDetailOutput(list); break; case WriteProxy.FLAG_SHOP_DETAIL: writeIntoExcelProxy.shopDetailOutput(list); break; case WriteProxy.FLAG_RENAME: writeIntoExcelProxy.renameOutput(list); break; case WriteProxy.FLAG_UNSALE: writeIntoExcelProxy.unSaleOutput(list); break; case WriteProxy.FLAG_UPDATE: writeIntoExcelProxy.itemUpdateOutput(list); break; case WriteProxy.FLAG_JUHUASUAN: writeIntoExcelProxy.juHuaSuanOutput(list); break; case WriteProxy.FLAG_ZUANSHI: writeIntoExcelProxy.zuanShiOutput(list); break; case WriteProxy.FLAG_TAOBAOKE: writeIntoExcelProxy.taobaokeOutput(list); break; } System.out.println(\u0026#34;开始写入文件。。。\u0026#34;); workbook.write(new FileOutputStream(file)); System.out.println(\u0026#34;写入完成!\u0026#34;); }catch (Exception e){ e.printStackTrace(); } return proxy; } 二是利用buidler模式构造Url参数\nstatic class ParamBuilder{ String paraStr = \u0026#34;\u0026#34;; public ParamBuilder addParam(String key, String value){ paraStr += \u0026#34;\u0026amp;\u0026#34;+key+\u0026#34;=\u0026#34;+value; return this; } public ParamBuilder addId(){ addParam(Key.id, Id); return this; } public ParamBuilder addShopId(){ addParam(Key.shopId, Id); return this; } public ParamBuilder addDate(){ addParam(Key.startDate, startDate); addParam(Key.endDate, endDate); return this; } public String build(){ return paraStr; } } 这里我们将分析数据和存储数据合并在了模拟请求里面,这是因为我们要做的这个爬虫比较简单,数据都是JSON的格式返回回的。如果数据格式比较复杂(如嵌在网页里),则需要构造正则表达式或者利用一些工具分析。同时,因为我们爬取的数据量不太而且不需要持久存储,所以我们直接输出在excel里面了。但是,实际应用中(如全站爬虫),爬取的数据量一般很大,而且需要持久存储、去重、更新维护等等。因此,我们一般会使用数据库,这会让存储数据变得复杂一些。\n遇到的问题和解决方案\r#\r问题:Gson解析错误,提示类型不匹配 原因:这个问题是由于GsonFormat产生的错误,由于price相关字段的数值为浮点型,但是我们取样的json里面的类型是int,所以会导致解析异常。 解决方法:只要将price相关的字段都改为double型即可。\n问题:Gson解析错误提示Unterminated object at line x column xxx 原因:根据堆栈日志可以定位到json发现,json里面存在类似title\u0026amp;quot;:\u0026amp;quot;\\\\\u0026amp;quot;Nike 耐克官方 NSW \\\\\u0026amp;quot;\\\\\u0026amp;quot;LET THERE BE AIR\\\\\u0026amp;quot;\\\\\u0026amp;quot; 大童(男孩)T恤 863808\\\\\u0026amp;quot;这种字符串,这明显是不符合JSON要求的。 解决方案:写一个正则表达式,把非法字符串替换成空串即可。 PS:吐槽一下,其实这是他们后台的一个bug,他们的页面也无法访问这种类型的数据,会提示错误。。。\n分析\r#\r至此,我们就实现了一个最简单的爬虫了。现在我们能够将一些接口的数据爬取回来并且写入本地excel文件了。在实现的过程中还利用了builder模式和JDK动态代理增加代码可读性和减少重复性代码。但是,爬虫的内容绝对不止这么简单,我们这个这么简单一方面是因为我们进行爬虫的网站没有做防爬虫机制。另外一方面,我们的爬取的数据是异步请求的,所以从获取到解析都是相对容易的。但是大量的网站的数据是同步获取的,数据都是嵌入在html代码里面,这个时候就需要我们分析html代码。 常见的防爬虫措施有以下几种:\n在网站的robot.txt文件里面声明哪些是爬虫可以访问的数据。 同一IP大量请求后,屏蔽该IP一段时间。 对于请求频率过高的请求要求验证码或谷歌人机验证。 豆瓣是防爬虫做得比较严格一个鲜明的例子。我曾经试过在正常浏览的情况下,就是因为浏览得快一点,就在短时间内被多次要求输入验证码。\n既然防爬虫有那么多措施,那么爬虫要怎么应对呢?其实也是有解决方案的。对于封IP的行为,采用公开的IP池的方式,每次使用不同的IP即可。对于请求频率过高要求验证的问题,第一,可以控制请求的频率,第二,人工输入验证码。显而易见,由于web是公开的特征,所以爬虫还是很难完全封掉的。因为你无法准确地判断服务器接收到的请求是爬虫发出的还是用户发出的,所以只能封禁一些明显是爬虫访问的请求,但是爬虫也可以不断地改进从而最大程度模拟用户请求的。\n结语\r#\r如上所述,这只是一个简单的爬虫例子,但是麻雀虽小,五张俱全,还是能完整体现整个爬虫的流程的。另外,java爬虫确实是有点累赘,而且大量Java Bean的创建也需要花费不少的时间。最近在学习python,学好之后可以使用python的爬虫框架scrapy去爬取一些带有防爬虫的和多重认证的网站,而且还可以把数据存储在数据库里面、做一些去重等等的工作。爬虫是一个很有趣的过程,有很多东西可以研究的。\n本次的爬虫就到这里啦,下次做了更复杂的爬虫可以再来分享一下。\n[1] 源代码:https://github.com/UZPENG/Crawler-Demo\n","date":"2017-12-26","externalUrl":null,"permalink":"/posts/crawl/","section":"文章","summary":"背景\r#\r历史博客已归档,关于爬虫的解读和导读详情见《漫谈博客来时路》\n最近女朋友需要利用tableau来做数据可视化的作业,要做数据可视化,那首先可定得要有数据。她们打算用八爪鱼等爬虫软件来抓取数据,结果没成功,原因不明。然后跟我吐槽了一下这个事情,刚好我最近也要学爬虫,本着项目驱动学习的理念,就直接选择了这个项目来练手了。\n准备工作\r#\r编程环境:\nIntelliJ IDEA 2017.1.5 Build #IU-171.4694.70, built on July 4, 2017 Licensed to weapon JRE: 1.8.0_112-release-736-b21 amd64 JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o Windows 10 10.0 编程环境如上所示,因为我Java语言比较熟悉,所以编程语言选择了Java。虽然Java做爬虫语法上面比较累赘,但是Python还没学会,就凑合着用Java写了。\n","title":"爬虫实践--淘数据网站的数据爬取和存储","type":"posts"},{"content":"","date":"2017-06-07","externalUrl":null,"permalink":"/page/","section":"","summary":"","title":"","type":"page"},{"content":"略懂代码,喜欢徒步,业余带娃爱好者。\n","date":"2017-03-04","externalUrl":null,"permalink":"/","section":"介绍","summary":"略懂代码,喜欢徒步,业余带娃爱好者。\n","title":"介绍","type":"page"},{"content":"V2版本已发布,详情见《重启博客杂谈》\n似乎自己从来都没有写过博客,能稍微沾上边的就初中的时候写qq空间的日志吧,感觉已经是半个世纪前的事情了。然后现在为什么又想写博客了呢?原因主要有以下几个。\n第一,上了大学以来,似乎都没什么做笔记的习惯了,无论是上课还是看书还是练习都是听了或看了或做了之后就算了,没有经过自己的再次思考去沉淀这些知识,所以想写博客来整理自己的思路和记录自己的思考。\n第二,现在网络上有很多资源,有时候看了之后第二次又忘了,所以需要markdown下来方便自己二次查询。\n第三,写文章能够让自己的心情静下来,其实是一件很放松的事情,所以写博客也是一种放松自己的途径。\n总得来说就是希望自己可以保持思考,用心学习!希望自己坚持下去!\n","date":"2017-03-04","externalUrl":null,"permalink":"/posts/start/","section":"文章","summary":"V2版本已发布,详情见《重启博客杂谈》\n似乎自己从来都没有写过博客,能稍微沾上边的就初中的时候写qq空间的日志吧,感觉已经是半个世纪前的事情了。然后现在为什么又想写博客了呢?原因主要有以下几个。\n","title":"我的博客之旅","type":"posts"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]