本文目录:
一、爬虫的目的
二、python爬虫的过程和步骤
1.发送请求和网页响应
2.解析网页内容
3.保存数据
三、在此过程中可能遇到的问题及解答
此文章适合爬虫小白(超新手),保姆级教学。此文同样发布在简书,我在简书的作者名:还在此处
一、爬虫的目的
Python爬虫的目的是更快捷地搜索查看网上的数据,并把数据保存下来进行分析。
二、python爬虫的过程和步骤
Python爬虫的过程:①向网站发送请求request②获得网站响应response③解析网站内容④保存数据方便下一步分析
首先,我们试着写一个最简单的爬虫——豆瓣电影top250的电影名、分值和评价人数
网址:
第一步和第二部:发送请求并获取网站响应
import requests#导入requests库 header={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"} Response=requests.get("https://movie.douban.com/top250",headers=header) print(Response)#返回http状态码,如果输出是200表示成功处理请求 R=Response.text
可以看到这get里面除了网址还有一个headers。如果没有这个headers,我们运行代码,返回的状态码是418,也就是说我们的请求被反爬程序给拒绝了。同样的,如果我们的headers有错误也会同样出现418.。
这个header是哪里来的?在网页中F12打开开发人员工具,选择网络/network,如果没有出现什么东西的话,需要再次刷新页面,点击网址,会出现请求标头(Request Headers),响应标头(Response Headers)等。
其实除了红框里面的User-Agent,请求标头里面所有的东西都属于Headers,不过一般情况只需要把User-Agent写上即可。写上Headers进行请求相当于模拟成网页去服务器浏览数据。还有在requests get()里面,有一个headers=header的部分,因为requests.get(url,params=None,**kwargs)有三个参数,为了指明区分,需要写成headers=xxx的形式。
除了只是输出上图的http状态码还能输出一下的东西。
#print(type(Response))#返回类型class’request.models.Response‘ #print(Response.text)#返回文本信息 #print(Response.content)#以二进制输出
于是我们就学完了爬虫的第一步发送请求和第二步获取网站响应了。
第三步:解析网站内容
首先我们F12选择元素/Element先看一下网站的代码
图一:
图二:
这一步很方便的就能找到想要的代码,把鼠标放到各个div上都会在网页上标出它们的位置。我们先把电影名称、分数和评价人数这几个信息的位置描述出来。
电影名称:在class=grid_view的ol标签下的li标签下的class=item的div标签下的class=info标签下的class=hd的div标签下的a标签下的class=title的span标签里的文字
分数:(前省略) 的class=bd的div标签下的class=star的div标签下的class=rating_num的span标签里的文字
人数:(前省略) 的span标签里的文字
找到位置后,我来介绍一下这些解析器
正则式、Beautiful Soup、Lxml
难度:正则式>Beautiful Soup≈Lxml
性能:正则式≈Lxml>Beautiful Soup
1.正则式
import re
首先导入re,然后开始findall
ho_name=re.findall(r'<div\sclass="hd">.*?<a.*?>.*?<span\sclass="title">(.*?)</span>',R,re.DOTALL) ho_score=re.findall(r'<div\sclass="bd">.*?<div\sclass="star">.*?<span\sclass="rating_num" property="v:average">(.*?)</span>',R,re.DOTALL) ho_peoplenum=re.findall(r'<div\sclass="bd">.*?<div\sclass="star">.*?<span>(.*?)</span>',R,re.DOTALL) print(ho_name) print(ho_score) print(ho_peoplenum)
如上面的代码所示,我们通过正则式<div\sclass="hd">.*?<a.*?>.*?<span\sclass="title">(.*?)</span>定位到了电影名称,其中正则式中的/s可以写成空格,.*?表示精确配对的非贪心算法(正则式的详细内容这里就不展开了容我下一篇章解释),这里我们先把.*?理解为一个内容的省略,(.*?)理解成我们的目标内容就可以了。所以正则式表示的是在class=hd的div标签下的.*?的a标签.*?下的class=title的span标签里有目标内容。也就是我们上面说到的电影名称的位置了。这样可以把分数和评论人数也分析出来。
把上图的语句进行一部分省略并把输出稍微变得好看一些就会得到下图
ho_name=re.findall(r'<a.*?>.*?<span\sclass="title">(.*?)</span>',R,re.DOTALL) ho_score=re.findall(r'<span\sclass="rating_num" property="v:average">(.*?)</span>',R,re.DOTALL) ho_peoplenum=re.findall(r'<div\sclass="star">.*?<span>(.*?)</span>',R,re.DOTALL) for i in range(0,len(ho_name)): print(ho_name[i]+","+ho_score[i]+","+ho_peoplenum[i])
这里简单的提一下,正则式的意思就是通过特殊匹配规则对字符串进行匹配,这上面的语句都是把正则式和R里面的东西匹配。如果我们print(R)会发现这个R就是我们在网页F12中看到的代码,也就是说正则式是从代码中比对并且获得元素的与我们下面看到的方法是异曲同工之妙。
2.Beautiful Soup
from bs4 import BeautifulSoup as bs
首先导入BeautifulSoup,然后把html内容翻译成bs可以理解的样子,即变成一颗标签树(R_tree=bs(R,"html.parser")),再从一个个标签中找到目标内容。
R_tree=bs(R,"html.parser") lis=R_tree.find("ol",{"class":"grid_view"}) list=lis.find_all("li") for ho in list: ho_name=ho.find("span",{"class":"title"}).text ho_score=ho.find("span",{"class":"rating_num"}).text ho_people_lis=ho.find("div",{"class":"star"}).find_all("span") ho_peoplenum=ho_people_lis[3].contents[0] print(ho_name + ";" + ho_score + ";" + ho_peoplenum )
首先我们要找到ol的标签,这个标签的class是grid_view,参考我上面说到的电影名称的位置描述。注意在BeautifulSoup中有find和find_all这两种基本的找法,find返回的是单个,而find_all返回的是列表。如果在某个标签下只有一个标签用find_all会在程序中报错。这里的li标签有多个所以用find_all。然后我用了for循环把他们找了出来并且输出,这里你可以看到打开了ol房间的门,里面有很多个li的纸箱,每个li的纸箱里都有香蕉和苹果,我们把每个箱子里的东西都检查一遍的感觉。而正则式更偏向于根据路牌找路,每条路的终点都有一个宝箱,这个宝箱装了很多东西,虽然每条路都要走到终点但只需要找三条路就行了。这样子理解还可以得出一个结论BeautifulSoup比正则式要幸苦所以它也比正则式慢。
在上图的程序中,可能会有一个不太明白的点
(ho_peoplenum=ho_people_lis[3].contents[0])。
这句话表达的意思是从列表中找到第三个span标签里的文字(从0开始数)。
3.Lxml
from lxml import etree
首先导入lxml,然后把html翻译成lxml可以理解的样子,再定位找到目标内容。
list = etree.HTML(html) lis = list.xpath('//ol[@class="grid_view"]/li') for ho in lis: ho_name = ho.xpath("div/div[2]/div[1]/a/span[1]/text()")[0] ho_score = ho.xpath("div/div[2]/div[2]/div/span[2]/text()")[0] ho_peoplenum = ho.xpath("div/div[2]/div[2]/div/span[4]/text()")[0] print(ho_name + ";" + ho_score + ";" + ho_peoplenum)
与bs相似的“找到”ol和li,只是相似并非相同,这里更像是定位到,而bs是找到,所以lxml比bs快。说说第一条语句(ho_name = ho.xpath("div/div[2]/div[1]/a/span[1]/text()")[0]
)表示电影名称在div里的第二个div(从1开始数)里的第一个div里的a里的第一个span里的文字。很清晰易懂(结合网页中的代码来看),顺理成章可以推理出分数和评论人数。
第四步:保存数据
data = open("E:\\data.txt", "w")
'''
第一、二、三步的代码内容
'''
data.write("电影名:%s,分数:%s,评论人数:%s \n" % (ho_name, hoe_score, ho_peoplenum))
写入文件算是python的基础,这里就不再啰嗦了。
三、在这个过程中你可能遇到的问题
1.明明已经下载了Lxml,却无法导入pycharm https://zhuanlan.zhihu.com/p/108127791
2.如果返回状态码是418:注意你从网站上复制下来的时候,user-agent中间是否有空格,如果有空格是错的。或者查看是否有其他错误导致被反爬程序发现了。
3.在查找分数的时候是否把正则式写成了<span\sclass="rating_num">(.*?)</span>导致没有找到对应的text,注意要把标签的属性写上。
4.bs=(R,“html.parser”)是否受headers=header的影响写成bs=(R,parser=“html.parser”)
2020-08-06