1.安装pip
我的个人桌面系统用的linuxmint,系统默认没有安装pip,考虑到后面安装requests模块使用pip,所以我这里第一步先安装pip。
1 |
|
安装成功,查看PIP版本:
1 |
|
2.安装requests模块
这里我是通过pip方式进行安装:
1 |
|
运行import requests,如果没提示错误,那说明已经安装成功了!
检验是否安装成功
3.安装beautifulsoup4
Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库。它能够通过你喜欢的转换器实现惯用的文档导航,查找、修改文档的方式。Beautiful Soup会帮你节省数小时甚至数天的工作时间。
1 |
|
注:这里我使用的是python3的安装方式,如果你用的是python2,可以使用下面命令安装。
1 |
|
4.requests模块浅析
1)发送请求
首先当然是要导入 Requests 模块:
1 |
|
然后,获取目标抓取网页。这里我以下为例:
1 |
|
这里返回一个名为 r 的响应对象。我们可以从这个对象中获取所有我们想要的信息。这里的get是http的响应方法,所以举一反三你也可以将其替换为put、delete、post、head。
2)传递URL参数
有时我们想为 URL 的查询字符串传递某种数据。如果你是手工构建 URL,那么数据会以键/值对的形式置于 URL 中,跟在一个问号的后面。例如, cnblogs.com/get?key=val。 Requests 允许你使用 params 关键字参数,以一个字符串字典来提供这些参数。
举例来说,当我们google搜索“python爬虫”关键词时,newwindow(新窗口打开)、q及oq(搜索关键词)等参数可以手工组成URL ,那么你可以使用如下代码:
1 2 3 |
|
3)响应内容
通过r.text或r.content来获取页面响应内容。
1 2 3 4 5 |
|
Requests 会自动解码来自服务器的内容。大多数 unicode 字符集都能被无缝地解码。这里补充一点r.text和r.content二者的区别,简单说:
resp.text返回的是Unicode型的数据;
resp.content返回的是bytes型也就是二进制的数据;
所以如果你想取文本,可以通过r.text,如果想取图片,文件,则可以通过r.content。
4)获取网页编码
1 2 3 4 5 |
|
5)获取响应状态码
我们可以检测响应状态码:
1 2 3 4 5 |
|
5.案例演示
并且只抓取页面中文章标题和内容等有用信息。
演示环境
操作系统:linuxmint
python版本:python 3.5.2
使用模块:requests、beautifulsoup4
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
一下为获取网页上特定的内容的程序 没有注释
import requests
from bs4 import BeautifulSoup
import bs4
import os
from time import sleep
url_list = []
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'}
def url_all():
for page in range(1,401):
url = 'http://blog.csdn.net/?ref=toolbar_logo&page='+str(page)
url_list.append(url)
def essay_url(): #找到所有文章地址
blog_urls = []
for url in url_list:
html = requests.get(url, headers=headers)
html.encoding = html.apparent_encoding
soup = BeautifulSoup(html.text, 'html.parser')
for h3 in soup.find_all('h3'):
blog_url = (h3('a')[0]['href'])
blog_urls.append(blog_url)
return blog_urls
def save_path():
s_path = 'D:/blog/'
if not os.path.isdir(s_path):
os.mkdir(s_path)
else:
pass
return s_path
def save_essay(blog_urls,s_path): #找到所有文章标题,文章内容。
for url in blog_urls:
blog_html = requests.get(url, headers=headers)
blog_html.encoding = blog_html.apparent_encoding
soup = BeautifulSoup(blog_html.text, 'html.parser')
try:
for title in soup.find('span', {'class': 'link_title'}):
if isinstance(title, bs4.element.Tag):
print('-----文章标题-----:', title.text)
blogname = title.text
blogname = blogname.replace("\n",'')
blogname = blogname.replace("\r",'')
blogname = blogname.replace(" ",'')
try:
file = open(s_path + str(blogname) + '.txt', 'w')
file.write(str(title.text))
file.close()
except BaseException as a:
print(a)
for p in soup.find('div', {'class': 'article_content'}).children:
if isinstance(p, bs4.element.Tag):
try:
file = open(s_path + str(blogname) + '.txt', 'a')
file.write(p.text)
file.close()
except BaseException as f:
print(f)
except BaseException as b:
print(b)
print('---------------所有页面遍历完成----------------')
sleep(10)
url_all()
save_essay(essay_url(),save_path())
代码
读入网页加以解析抓取,需要用到的软件包是 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://goo.gl/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 标记,进入它管辖的这个区域后去找 div.note
标记,然后找……最后找到 a 标记,这里就是要找的内容了。
回到咱们的 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):
mylist = []
try:
results = r.html.find(sel)
for result in results:
mytext = result.text
mylink = list(result.absolute_links)[0]
mylist.append((mytext, mylink))
return mylist
except:
return None
我们测试一下这个函数。
还是用刚才的标记路径(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")后冒号后内容有区别。
这就是我们自动化的关键了。
上述两个标记路径里面,因为指定了在第几个“子”(nth-child
)文本段(paragraph,也就是"p"代表的含义)去找"a"这个标记,因此只返回来单一结果。
如果我们不限定"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自动网页抓取的基础技能。希望阅读并动手实践后,你能掌握以下知识点:
- 网页抓取与网络爬虫之间的联系与区别;
- 如何用 pipenv 快速构建指定的 Python 开发环境,自动安装好依赖软件包;
- 如何用 Google Chrome 的内置检查功能,快速定位感兴趣内容的标记路径;
- 如何用 requests-html 包来解析网页,查询获得需要的内容元素;
- 如何用 Pandas 数据框工具整理数据,并且输出到 Excel。
或许,你觉得这篇文章过于浅白,不能满足你的要求。
文中只展示了如何从一个网页抓取信息,可你要处理的网页成千上万啊。
别着急。
本质上说,抓取一个网页,和抓取10000个网页,在流程上是一样的。
而且,从咱们的例子里,你是不是已经尝试了抓取链接?
有了链接作为基础,你就可以滚雪球,让Python爬虫“爬”到解析出来的链接上,做进一步的处理。
将来,你可能还要应对实践场景中的一些棘手问题:
- 如何把抓取的功能扩展到某一范内内的所有网页?
- 如何爬取Javascript动态网页?
- 假设你爬取的网站对每个IP的访问频率做出限定,怎么办?
- ……
这些问题的解决办法,我希望在今后的教程里面,一一和你分享。
需要注意的是,网络爬虫抓取数据,虽然功能强大,但学习与实践起来有一定门槛。
当你面临数据获取任务时,应该先检查一下这个清单:
- 有没有别人已经整理好的数据集合可以直接下载?
- 网站有没有对你需要的数据提供API访问与获取方式?
- 有没有人针对你的需求,编好了定制爬虫,供你直接调用?
如果答案是都没有,才需要你自己编写脚本,调动爬虫来抓取。
为了巩固学习的知识,请你换一个其他网页,以咱们的代码作为基础修改后,抓取其中你感兴趣的内容。
如果能把你抓取的过程记录下来,在评论区将记录链接分享给大家,就更好了。
因为刻意练习是掌握实践技能的最好方式,而教是最好的学。
祝顺利!
思考
本文主要内容讲解完毕。
这里给你提一个疑问,供你思考:
我们解析并且存储的链接,其实是有重复的:
这并不是我们的代码有误,而是在《如何用《玉树芝兰》入门数据科学?》一文里,本来就多次引用过一些文章,所以重复的链接就都被抓取出来了
作者:王树义
链接:https://www.jianshu.com/p/ba02079ecd2f
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。