从开始玩爬虫到现在差不多半年多了,写了不少爬虫,爬了不少网站,在博客里也分享了不少爬虫的教程。
我的教程文章中,一般会附带完整的爬虫代码,大家只要搭建好环境,便可以直接运行使用。不少读者朋友在使用爬虫遇到问题时也会跟我讨论,交流过程中我发现了一些比较共性的问题。
因此文章重点放在了思路分析上,具体如何编写代码爬取数据则简单略过。造成了一些读者,基于我的代码进行修改爬取其他相似网站时束手无策。
由于写代码时有些疏忽,以及没有考虑到各人系统环境默认编码等设置的差异,导致读者们运行代码时出现了一些常见的 Bug,比如爬取到的数据乱码问题,保存文件时路径错误等问题。
针对这些问题,我打算将我玩爬虫时的一些经验心得,供大家参考,如果有哪里说的不对,或者有更好的方法的话,欢迎大佬们批评指正,交流补充。
1. 设置请求头 Headers 的问题
- 可以通过设置 headers 将爬虫伪装成浏览器,现在大部分的网站不这样伪装一下根本爬不了。
headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
}
- 一般 headers 设置 user-Agent 即可,如果有的数据是登陆后才能看到的话,还需要添加 cookies 参数(先登陆账号后,在浏览器的开发者工具中,拷贝 Cookies 即可)。这些参数都可以在浏览器的开发者工具中找到。
- 注意,如果要分享代码给别人的话,代码中的 cookie 数据一定要删掉,否则别人可以通过这个直接免密登陆你的账户。
2. 为什么我爬到的数据是乱码?
一般大家看到乱码,下意识的觉得是代码有问题爬错东西了。其实不然,这个就是编码的问题,解决方式也很简单。
程序中涉及到编码格式的地方有两处,一处是在发起请求后,对返回的内容进行解码;另一处是在保存文件时,设置编码格式。下面我们分开来说。
- 发起请求,获取网页内容阶段。一般的网站的编码格式都是 UTF-8,所以当你系统的默认编码也是 UTF-8 时,也就是说,你的默认编码方式和目标网站的编码方式一致时,即使不明确设置编码方式,也不会出问题。但是如果不一致,便会出现乱码。这也是为什么经常有 “明明在我电脑上运行是好的,为什么在你电脑上就乱码了” 这样的问题。
这种问题解决也很简单,只要在代码中设置一下 encoding 即可。
这里建议一种方法,r.encoding = r.apparent_encoding
,这个可以自动推测目标网站的编码格式,省的你自己去一个个设置(当然极少数情况下它可能会推测错误出现乱码,到时候你再手动去查看网页编码,手动设置吧)。
def fetchURL(url):
headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
}
r = requests.get(url,headers=headers)
r.raise_for_status()
# 这里设置编码格式
r.encoding = r.apparent_encoding
return r.text
- 保存文件时的编码错误。
这个是读者朋友们反映较多的一个问题,就是爬取过程中没问题,但是用 excel 打开保存好的 csv 文件时出现乱码(用记事本打开没问题)。
这个其实就是文件的编码方式和 Excel 的解码方式不一致导致的。在 dataframe.to_csv 这句,参数里添加一个encoding='utf_8_sig'
,指定文件的编码格式,应该就可以解决了。
def writePage(urating):
'''
Function : To write the content of html into a local file
'''
import pandas as pd
dataframe = pd.DataFrame(urating)
dataframe.to_csv('filename.csv',encoding='utf_8_sig', mode='a', index=False, sep=',', header=False )
之前乱码的 csv 文件,可以用记事本打开,然后点另存为,然后选择编码格式,ANSI ,unicode,UTF-8 都可以,然后保存之后,再次用 excel 打开就是正常的了。
如果你不知道你的乱码是哪种问题,有一个简单的判断方法,就是用记事本打开 csv 文件,如果正常显示,那么无误就是第二种情况,如果是乱码,那么很有可能是第一种情况。
3. 解析网页时,我如何快速找到数据存放的位置,并提取其中的数据?
这个也是很多读者朋友遇到的问题,就是虽然运行我的代码没有问题,大概逻辑也能读得懂,但是想修改一下爬取同类网站,或者同一个网站的其他数据时,却束手无策,不知从何下手。
这个其实是对工具使用不熟悉而已,我这里简单讲解一下(beautifulSoup)常用的使用技巧(当然它有很多强大便捷的功能,我这里只介绍几个常用的,很好用的函数,这几个用好了一样可以应付几乎所有网站)
首先,爬取之前需要定位到数据所在的标签,这个使用 F12 开发者工具中的这个按钮,点一下按钮,然后点一下网页,可以很快定位到页面中的相应标签,具体就不详细说了,自己摸索一下,很简单,很好用的。
接下来正式介绍,如何用代码获取到前面找到的那个标签。
这里介绍 beautifulSoup 中的两个函数,find 和 find_all 函数。
首先你观察你要找到的标签,是什么标签,是否有 class 或者 id 这样的属性(如果没有就找找它父标签有没有,尽量找这样的),因为 class 和 id 这两个属性作为筛选条件的话,查找到的干扰项极少,运气好的话,基本上可以一击必中。
比如我们要获取上图中箭头所指的,id 为 ozoom 的 div 标签时,我们可以这样
# html 是之前发起请求获取到的网页内容
bsobj = bs4.BeautifulSoup(html,'html.parser')
# 获取 id 为 ozoom 的 div 标签
# 根据 id 查找标签
div = bsobj.find('div', attrs = {'id' : 'ozoom'})
# 继续获取 div 下的 class 为 list_t 的 div 标签
# 根据 class 查找标签
title = div.find('div', attrs = {'class': 'list_t'})
注:如果标签有 id 属性的话尽量用 id 来查找,因为整个页面 id 是唯一的。用 class 查找的话,最好现在浏览器的网页源码中 Ctrl + F 搜索一下,相同 class 的标签有多少(如果比较多的话,可以尝试先查找他的父标签,缩小范围之后再查找)。
然后我们再讲讲 find_all 函数,适用于一次性查找一类型的很多标签的情况,比如下图这种情况
列表中的每一个 li 标签中,都是一条数据,我们需要将它们都获取到,如果是用前面的 find 函数的话,每次只能获取一个 li 标签。所以我们需要使用 find_all 函数,一次性获取所有符合条件的标签,存储为数组返回。
首先,由于 li 标签没有 id 也没有 class ,而页面中存在很多无关的干扰的 li 标签,所以我们需要先从它的父标签往上找,缩小查找范围,找到 id 为 titleList 的 div 标签之后,观察一下,里面的 li 标签都是需要的,直接 find_all 函数一下都获取完。
# html 是获取的目标网页内容
html = fetchUrl(pageUrl)
bsobj = bs4.BeautifulSoup(html,'html.parser')
pDiv = bsobj.find('div', attrs = {'id': 'titleList'})
titleList = pDiv.find_all('li')
基本上,把 find 和 find_all 函数组合使用,用熟练了可以应付几乎所有的 html 网页了,真的是,一招鲜吃遍天。
4. 查找到标签之后,我该如何获取标签中的数据呢?
标签中的数据位置,一般有两种情况。
<!--第一种,位于标签内容里-->
<p>这是数据这是数据</p>
<!--第二种,位于标签属性里-->
<a href="/xxx.xxx_xx_xx.html"></a>
如果是第一种情况,很简单,直接 pTip.text
即可(pTip 是前面已经获取好的 p 标签)。
如果是第二种情况,需要看它是在哪一个属性里的数据,比如我们要获取上面 a 标签中的 href 属性中的链接,可以 link = aTip["href"]
即可。(aTip 是前面已经获取好的 a 标签)。
5. 有些数据不能直接用,我需要从中提取想要的关键数据,但是我正则表达式不太会怎么办?
使用正则表达式处理字符串,用好了确实很厉害,但是门槛也相对较高。不仅学起来麻烦还容易忘,于是我就发现,其实一些简单的功能,使用字符串自带的 拼接 ,分割(split)等功能就可以很好地实现。
比如我获取到一个网址链接 html/2019-05/06
,我需要从中提取出包含的年、月、日信息。我可以这样。
url = 'html/2019-05/06'
# 先根据 / 分割,得到 group1
group1 = url.split('/')
# 分割完后,group1 = ["html","2019-05","06"]
day = group1[2] # day = "06"
# 将 group1[1],也就是 2019-05 进行分割,根据 - 分割成 group2
group2 = group1[1].split("-")
# 分割完成后,group2 = ["2019","05"]
year = group2[0] # year = "2019"
month = group2[1] # month = "05"
除了正则表达式之外,我比较喜欢用这个,小小的一个函数,用的熟练之后,还是能做不少事情的。
6. 为什么保存文件时候会报错 “FileNotFoundError:No such file or directory” 呢?
Python 在保存文件时,如果路径下你要操作的文件不存在,它会自动创建一个文件,然后写入数据。
但是,如果是路径中的文件夹不存在,则不会自动创建,而是会报错上面那样的错误。
所以啊,以后看到这样的错误,不要慌,只是你的路径中没有对应的文件夹而已,缺哪个文件夹,自己手动创建好,再运行就好啦。
如果你跟我一样比较懒,想让代码自动创建文件夹,也很简单,只需要在保存文件前,执行这些代码即可。
import os
# 如果没有该文件夹,则自动生成
if not os.path.exists(path):
os.makedirs(path)
7. 最后再说一说爬虫的一些原则
- 指定爬取策略时,尽可能少的发起网络请求。一次请求能解决的事儿,尽量不要多次。
- 控制发起请求的频率,过于频繁的访问,不仅给对方服务器带来压力,也容易被对方反爬虫系统针对。
- 写爬虫要心怀敬畏之心,不当使用爬虫容易触犯法律。
- 爬虫毕竟只是工具,是手段,真正重要东西的数据,所以,不要为了使用爬虫而使用爬虫,数据量较小,能手动解决的尽量手动解决。