你期待已久的Python网络数据爬虫教程来了。本文为你演示如何从网页里找到感兴趣的链接和说明文字,抓取并存储到Excel。 需求我在公众号后台,经常可以收到读者的留言。 很多留言,是读者的疑问。只要有时间,我都会抽空尝试解答。 但是有的留言,乍看起来就不明所以了。 例如下面这个: 一分钟后,他可能觉得不妥(大概因为想起来,我用简体字写文章),于是又用简体发了一遍。 我恍然大悟。 这位读者以为我的公众号设置了关键词推送对应文章功能。所以看了我的其他数据科学教程后,想看“爬虫”专题。 不好意思,当时我还没有写爬虫文章。 而且,我的公众号暂时也没有设置这种关键词推送。 主要是因为我懒。 这样的消息接收得多了,我也能体察到读者的需求。不止一个读者表达出对爬虫教程的兴趣。 之前提过,目前主流而合法的网络数据收集方法,主要分为3类:
前两种方法,我都已经做过一些介绍,这次说说爬虫。 概念许多读者对爬虫的定义,有些混淆。咱们有必要辨析一下。 维基百科是这么说的:
这问题就来了,你又不打算做搜索引擎,为什么对网络爬虫那么热心呢? 其实,许多人口中所说的爬虫(web crawler),跟另外一种功能“网页抓取”(web scraping)搞混了。 维基百科上,对于后者这样解释:
看到没有,即便你用浏览器手动拷贝数据下来,也叫做网页抓取(web scraping)。是不是立刻觉得自己强大了很多? 但是,这定义还没完:
也就是说,用爬虫(或者机器人)自动替你完成网页抓取工作,才是你真正想要的。 数据抓下来干什么呢? 一般是先存储起来,放到数据库或者电子表格中,以备检索或者进一步分析使用。 所以,你真正想要的功能是这样的: 找到链接,获得Web页面,抓取指定信息,存储。 这个过程有可能会往复循环,甚至是滚雪球。 你希望用自动化的方式来完成它。 了解了这一点,你就不要老盯着爬虫不放了。爬虫研制出来,其实是为了给搜索引擎编制索引数据库使用的。你为了抓取点儿数据拿来使用,已经是大炮轰蚊子了。 要真正掌握爬虫,你需要具备不少基础知识。例如HTML, CSS, Javascript, 数据结构…… 这也是为什么我一直犹豫着没有写爬虫教程的原因。 不过这两天,看到王烁主编的一段话,很有启发:
既然我们的目标很明确,就是要从网页抓取数据。那么你需要掌握的最重要能力,是拿到一个网页链接后,如何从中快捷有效地抓取自己想要的信息。 掌握了它,你还不能说自己已经学会了爬虫。 但有了这个基础,你就能比之前更轻松获取数据了。特别是对“文科生”的很多应用场景来说,非常有用。这就是赋能。 而且,再进一步深入理解爬虫的工作原理,也变得轻松许多。 这也算“另类二八定律”的一个应用吧。 Python语言的重要特色之一,就是可以利用强大的软件工具包(许多都是第三方提供)。你只需要编写简单的程序,就能自动解析网页,抓取数据。 本文给你演示这一过程。 目标要抓取网页数据,我们先制订一个小目标。 目标不能太复杂。但是完成它,应该对你理解抓取(Web Scraping)有帮助。 就选择我最近发布的一篇简书文章作为抓取对象好了。题目叫做《如何用《玉树芝兰》入门数据科学?》。 这篇文章里,我把之前的发布的数据科学系列文章做了重新组织和串讲。 文中包含很多之前教程的标题和对应链接。例如下图红色边框圈起来的部分。 假设你对文中提到教程都很感兴趣,希望获得这些文章的链接,并且存储到Excel里,就像下面这个样子: 你需要把非结构化的分散信息(自然语言文本中的链接),专门提取整理,并且存储下来。 该怎么办呢? 即便不会编程,你也可以全文通读,逐个去找这些文章链接,手动把文章标题、链接都分别拷贝下来,存到Excel表里面。 但是,这种手工采集方法没有效率。 我们用Python。 环境要装Python,比较省事的办法是安装Anaconda套装。 请到这个网址下载Anaconda的最新版本。 请选择左侧的 Python 3.6 版本下载安装。 如果你需要具体的步骤指导,或者想知道Windows平台如何安装并运行Anaconda命令,请参考我为你准备的视频教程。 安装好Anaconda之后,请到这个网址下载本教程配套的压缩包。 下载后解压,你会在生成的目录(下称“演示目录”)里面看到以下三个文件。 打开终端,用cd命令进入该演示目录。如果你不了解具体使用方法,也可以参考视频教程。 我们需要安装一些环境依赖包。 首先执行: pip install pipenv 这里安装的,是一个优秀的 Python 软件包管理工具 pipenv 。 安装后,请执行: pipenv install 看到演示目录下两个Pipfile开头的文件了吗?它们就是 pipenv 的设置文档。 pipenv 工具会依照它们,自动为我们安装所需要的全部依赖软件包。 上图里面有个绿色的进度条,提示所需安装软件数量和实际进度。 装好后,根据提示我们执行: pipenv shell 此处请确认你的电脑上已经安装了 Google Chrome 浏览器。 我们执行: jupyter notebook 默认浏览器(Google Chrome)会开启,并启动 Jupyter 笔记本界面: 你可以直接点击文件列表中的第一项ipynb文件,可以看到本教程的全部示例代码。 你可以一边看教程的讲解,一边依次执行这些代码。 但是,我建议的方法,是回到主界面下,新建一个新的空白 Python 3 笔记本。 请跟着教程,一个个字符输入相应的内容。这可以帮助你更为深刻地理解代码的含义,更高效地把技能内化。 准备工作结束,下面我们开始正式输入代码。 代码读入网页加以解析抓取,需要用到的软件包是 requests_html 。我们此处并不需要这个软件包的全部功能,只读入其中的 HTMLSession 就可以。 from requests_html import HTMLSession 然后,我们建立一个会话(session),即让Python作为一个客户端,和远端服务器交谈。 session = HTMLSession() 前面说了,我们打算采集信息的网页,是《如何用《玉树芝兰》入门数据科学?》一文。 我们找到它的网址,存储到url变量名中。 url = 'https://www.jianshu.com/p/85f4624485b9' 下面的语句,利用 session 的 get 功能,把这个链接对应的网页整个儿取回来。 r = session.get(url) 网页里面都有什么内容呢? 我们告诉Python,请把服务器传回来的内容当作HTML文件类型处理。我不想要看HTML里面那些乱七八糟的格式描述符,只看文字部分。 于是我们执行: print(r.html.text) 这就是获得的结果了: 我们心里有数了。取回来的网页信息是正确的,内容是完整的。 好了,我们来看看怎么趋近自己的目标吧。 我们先用简单粗暴的方法,尝试获得网页中包含的全部链接。 把返回的内容作为HTML文件类型,我们查看 links 属性: r.html.links 这是返回的结果: 这么多链接啊! 很兴奋吧? 不过,你发现没有?这里许多链接,看似都不完全。例如第一条结果,只有: '/' 这是什么东西?是不是链接抓取错误啊? 不是,这种看着不像链接的东西,叫做相对链接。它是某个链接,相对于我们采集的网页所在域名(https://www.jianshu.com)的路径。 这就好像我们在国内邮寄快递包裹,填单子的时候一般会写“XX省XX市……”,前面不需要加上国家名称。只有国际快递,才需要写上国名。 但是如果我们希望获得全部可以直接访问的链接,怎么办呢? 很容易,也只需要一条 Python 语句。 r.html.absolute_links 这里,我们要的是“绝对”链接,于是我们就会获得下面的结果: 这回看着是不是就舒服多了? 我们的任务已经完成了吧?链接不是都在这里吗? 链接确实都在这里了,可是跟我们的目标是不是有区别呢? 检查一下,确实有。 我们不光要找到链接,还得找到链接对应的描述文字呢,结果里包含吗? 没有。 结果列表中的链接,都是我们需要的吗? 不是。看长度,我们就能感觉出许多链接并不是文中描述其他数据科学文章的网址。 这种简单粗暴直接罗列HTML文件中所有链接的方法,对本任务行不通。 那么我们该怎么办? 我们得学会跟 Python 说清楚我们要找的东西。这是网页抓取的关键。 想想看,如果你想让助手(人类)帮你做这事儿,怎么办? 你会告诉他: “寻找正文中全部可以点击的蓝色文字链接,拷贝文字到Excel表格,然后右键复制对应的链接,也拷贝到Excel表格。每个链接在Excel占一行,文字和链接各占一个单元格。” 虽然这个操作执行起来麻烦,但是助手听懂后,就能帮你执行。 同样的描述,你试试说给电脑听……不好意思,它不理解。 因为你和助手看到的网页,是这个样子的。 电脑看到的网页,是这个样子的。 为了让你看得清楚源代码,浏览器还特意对不同类型的数据用了颜色区分,对行做了编号。 数据显示给电脑时,上述辅助可视功能是没有的。它只能看见一串串字符。 那可怎么办? 仔细观察,你会发现这些HTML源代码里面,文字、图片链接内容前后,都会有一些被尖括号括起来的部分,这就叫做“标记”。 所谓HTML,就是一种标记语言(超文本标记语言,HyperText Markup Language)。 标记的作用是什么?它可以把整个的文件分解出层次来。 (图片来源:https:///kWCqS6) 如同你要发送包裹给某个人,可以按照“省-市-区-街道-小区-门牌”这样的结构来写地址,快递员也可以根据这个地址找到收件人。 同样,我们对网页中某些特定内容感兴趣,可以依据这些标记的结构,顺藤摸瓜找出来。 这是不是意味着,你必须先学会HTML和CSS,才能进行网页内容抓取呢? 不是的,我们可以借助工具,帮你显著简化任务复杂度。 这个工具,Google Chrome浏览器自带。 我们在样例文章页面上,点击鼠标右键,在出现的菜单里面选择“检查”。 这时,屏幕下方就会出现一个分栏。 我们点击这个分栏左上角(上图红色标出)的按钮。然后把鼠标悬停在第一个文内链接(《玉树芝兰》)上面,点击一下。  此时,你会发现下方分栏里面,内容也发生了变化。这个链接对应的源代码被放在分栏区域正中,高亮显示。 确认该区域就是我们要找的链接和文字描述后,我们鼠标右键选择高亮区域,并且在弹出的菜单中,选择 Copy -> Copy selector。 找一个文本编辑器,执行粘贴,就可以看见我们究竟复制下来了什么内容。 body > div.note > div.post > div.article > div.show-content > div > p:nth-child(4) > a 这一长串的标记,为电脑指出了:请你先找到 body 标记,进入它管辖的这个区域后去找 回到咱们的 Jupyter Notebook 中,用刚才获得的标记路径,定义变量sel。 sel = 'body > div.note > div.post > div.article > div.show-content > div > p:nth-child(4) > a' 我们让 Python 从返回内容中,查找 sel 对应的位置,把结果存到 results 变量中。 results = r.html.find(sel) 我们看看 results 里面都有什么。 results 这是结果: [<Element 'a' href='https://www.jianshu.com/nb/130182' target='_blank'>] results 是个列表,只包含一项。这一项包含一个网址,就是我们要找的第一个链接(《玉树芝兰》)对应的网址。 可是文字描述“《玉树芝兰》”哪里去了? 别着急,我们让 Python 显示 results 结果数据对应的文本。 results[0].text 这是输出结果: '玉树芝兰' 我们把链接也提取出来: results[0].absolute_links 显示的结果却是一个集合。 {'https://www.jianshu.com/nb/130182'} 我们不想要集合,只想要其中的链接字符串。所以我们先把它转换成列表,然后从中提取第一项,即网址链接。 list(results[0].absolute_links)[0] 这次,终于获得我们想要的结果了: 'https://www.jianshu.com/nb/130182' 有了处理这第一个链接的经验,你信心大增,是吧? 其他链接,也无非是找到标记路径,然后照猫画虎嘛。 可是,如果每找一个链接,都需要手动输入上面这若干条语句,那也太麻烦了。 这里就是编程的技巧了。重复逐条运行的语句,如果工作顺利,我们就要尝试把它们归并起来,做个简单的函数。 对这个函数,只需给定一个选择路径(sel),它就把找到的所有描述文本和链接路径都返回给我们。 def get_text_link_from_sel(sel): 我们测试一下这个函数。 还是用刚才的标记路径(sel)不变,试试看。 print(get_text_link_from_sel(sel)) 输出结果如下: [('玉树芝兰', 'https://www.jianshu.com/nb/130182')] 没问题,对吧? 好,我们试试看第二个链接。 我们还是用刚才的方法,使用下面分栏左上角的按钮点击第二个链接。 下方出现的高亮内容就发生了变化: 我们还是用鼠标右键点击高亮部分,拷贝出 selector。 然后我们直接把获得的标记路径写到 Jupyter Notebook 里面。 sel = 'body > div.note > div.post > div.article > div.show-content > div > p:nth-child(6) > a' 用我们刚才编制的函数,看看输出结果是什么? print(get_text_link_from_sel(sel)) 输出如下: [('如何用Python做词云?', 'https://www.jianshu.com/p/e4b24a734ccc')] 检验完毕,函数没有问题。 下一步做什么? 你还打算去找第三个链接,仿照刚才的方法做? 那你还不如全文手动摘取信息算了,更省事儿一些。 我们要想办法把这个过程自动化。 对比一下刚刚两次我们找到的标记路径: body > div.note > div.post > div.article > div.show-content > div > p:nth-child(4) > a 以及: body > div.note > div.post > div.article > div.show-content > div > p:nth-child(6) > a 发现什么规律没有? 对,路径上其他的标记全都是一样的,唯独倒数第二个标记('p')后冒号后内容有区别。 这就是我们自动化的关键了。 上述两个标记路径里面,因为指定了在第几个“子”( 如果我们不限定'p'的具体位置信息呢? 我们试试看,这次保留标记路径里面其他全部信息,只修改'p'这一点。 sel = 'body > div.note > div.post > div.article > div.show-content > div > p > a' 再次运行我们的函数: print(get_text_link_from_sel(sel)) 这是输出结果: 好了,我们要找的内容,全都在这儿了。 但是,我们的工作还没完。 我们还得把采集到的信息输出到Excel中保存起来。 还记得我们常用的数据框工具 Pandas 吗?又该让它大显神通了。 import pandas as pd 只需要这一行命令,我们就能把刚才的列表变成数据框: df = pd.DataFrame(get_text_link_from_sel(sel)) 让我们看看数据框内容: df 内容没问题,不过我们对表头不大满意,得更换为更有意义的列名称: df.columns = ['text', 'link'] 再看看数据框内容: df 好了,下面就可以把抓取的内容输出到Excel中了。 Pandas内置的命令,就可以把数据框变成csv格式,这种格式可以用Excel直接打开查看。 df.to_csv('output.csv', encoding='gbk', index=False) 注意这里需要指定encoding(编码)为gbk,否则默认的utf-8编码在Excel中查看的时候,有可能是乱码。 我们看看最终生成的csv文件吧。 很有成就感,是不是? 小结本文为你展示了用Python自动网页抓取的基础技能。希望阅读并动手实践后,你能掌握以下知识点:
或许,你觉得这篇文章过于浅白,不能满足你的要求。 文中只展示了如何从一个网页抓取信息,可你要处理的网页成千上万啊。 别着急。 本质上说,抓取一个网页,和抓取10000个网页,在流程上是一样的。 而且,从咱们的例子里,你是不是已经尝试了抓取链接? 有了链接作为基础,你就可以滚雪球,让Python爬虫“爬”到解析出来的链接上,做进一步的处理。 将来,你可能还要应对实践场景中的一些棘手问题:
这些问题的解决办法,我希望在今后的教程里面,一一和你分享。 需要注意的是,网络爬虫抓取数据,虽然功能强大,但学习与实践起来有一定门槛。 当你面临数据获取任务时,应该先检查一下这个清单:
如果答案是都没有,才需要你自己编写脚本,调动爬虫来抓取。 为了巩固学习的知识,请你换一个其他网页,以咱们的代码作为基础修改后,抓取其中你感兴趣的内容。 如果能把你抓取的过程记录下来,在评论区将记录链接分享给大家,就更好了。 因为刻意练习是掌握实践技能的最好方式,而教是最好的学。 祝顺利! 思考本文主要内容讲解完毕。 这里给你提一个疑问,供你思考: 我们解析并且存储的链接,其实是有重复的: 这并不是我们的代码有误,而是在《如何用《玉树芝兰》入门数据科学?》一文里,本来就多次引用过一些文章,所以重复的链接就都被抓取出来了。 但是你存储的时候,也许不希望保留重复链接。 这种情况下,你该如何修改代码,才能保证抓取和保存的链接没有重复呢? 讨论你对Python爬虫感兴趣吗?在哪些数据采集任务上使用过它?有没有其他更高效的方式,来达成数据采集目的?欢迎留言,把你的经验和思考分享给大家,我们一起交流讨论。 如果你对我的文章感兴趣,欢迎点赞,并且微信关注和置顶我的公众号“玉树芝兰”(nkwangshuyi)。 如果本文可能对你身边的亲友有帮助,也欢迎你把本文通过微博或朋友圈分享给他们。让他们一起参与到我们的讨论中来。 |
|
来自: ggggkkkkgggg > 《python》