本文通过介绍基本的html、计算机网络等知识,带领读者了解访问网页时发生了什么;并通过“爬取豆瓣Top250电影信息”的项目,向读者展示了一个最基本的爬虫过程。本文适合作为读者入门爬虫的文章,需要读者有一定的Python语法基础(包括但不限于条件判断、循环语句、定义函数、基本数据类型、函数调用等知识)。
作者的开发环境如下:
我们日常使用浏览器时,只需在浏览器最上方的地址栏输入目标网址,按下回车,即可访问一个网页。看似简单的一个操作,其实暗藏了许多繁琐的步骤。下面,我们通过一个简单的例子,来了解浏览器帮助我们完成了哪些操作:
阿里DNS: 180.76.76.76
百度DNS: 223.5.5.5
谷歌DNS: 8.8.8.8
Status Code:状态码,200代表成功与目标服务器建立连接;404代表服务器无反应;418代表触发了反爬机制,禁止访问
User-Agent:用户代理,即小明使用的浏览器告诉目标服务器,我们是哪种浏览器(小明使用的浏览器是Chrome,但Chrome却告诉目标服务器,我是Mozilla等等浏览器,这里出现的问题与各大浏览器厂商间的一段爱恨情仇有关,有兴趣的同学可以自行搜索了解)
至此,即完成了一个简单的浏览网页过程,可以看出。浏览器主要的工作是将用户输入的URL映射到IP地址;与服务器建立连接;传输数据;解析数据以及关闭连接。
下面介绍几个基本的概念:
通过第一节,已经了解到,我们看到的网页(即看到的图片、文字等)是存在一个html文件里,通过服务器发送给浏览器,浏览器再解析、展现给我们。
如果我们把页面保存下载到本地,是不是可以把重要的数据存下来了呢?答案是可以的,但是这种文件内存在着大量的超文本标记语言,不利于我们获取信息。(例如"https://www.baidu.com/"的网页源码如下)
假设我们想编写一个JAVA程序,把"https://www.baidu.com/"的网页源码解析、去除超文本标记语言(等),只保留页面左上角的所有字符串,即新闻、hao123、地图、视频、贴吧、学术、更多,这7个字符串,并输出到编程语言的控制台。
有一种思路可能是:把html文本转换格式成txt(内容不会变),把txt按行读取到Java中的变量,再编写一个函数(输入字符串,内部通过判断字符串两端是否含有"<"、">",输出该字符串是否为超文本标记语言),循环遍历所有的字符串,借用函数判断该字符串是否为超文本标记语言(如果是,则删去),对留下的字符串进行输出。
上述构思符合多数只入门了编程语言的同学,看似合理,其实输出后的内容与我们的预期大不相同,其中有一点原因就是:去除超文本标记语言后,页面仍有许多其他信息,比如
因此我们希望有一个近乎完美的工具,能够帮我们自动获取服务器的html文件,并解析、去除超文本标记语言(只留下文本),并按要求自动筛取数据。此时,这个工具便是爬虫了! 一个简单的爬虫程序基本分为以下几步:
通过上文的介绍,我们已经基本熟悉了爬虫项目的架构,那么下面我们就从一个最简单的例子,来学习编写爬虫。
# 加载包
import bs4 # 网页解析,获得数据
import re # 正则表达式,进行文字匹配
import urllib.request,urllib.error # 指定URL,获取网页数据
import xlwt # 进行excel操作
from bs4 import BeautifulSoup
# 主函数,用于调用各个函数
def main():
# 爬取网页获取数据函数,输入一个url地址,返回解析、去除无关信息后的数据
def getData(baseurl):
# 得到指定一个URL的网页内容(html文件)的数据,输入一个url地址
def askURL(url):
# 保存数据函数,输入为数据列表、保存路径
def saveData(datalist,savepath):
# 定义程序执行入口,将主函数写在内
if __name__ == "__main__":
if __name__ == "__main__":
print('开始执行程序')
main()
print('爬取完毕')
def main():
# 定义一个基础的URL
baseurl = 'https://movie.douban.com/top250?start='
def askURL(url):
# 利用字典的数据格式,保存用户代理信息到head变量,用于模拟浏览器
# 如果不自定义设置我们发送的请求中的User-Agent信息
# Python会自动帮我们设置成User-Agent: Python -version
# 这会触发服务器的反爬机制,禁止访问
head = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36'
}
# 利用urllib.request.Request()方法,把URL和自定义User-Agent封装成一个可以发送到目标服务器的请求
request = urllib.request.Request(url, headers=head)
# 定义一个空字符串,用于后续接受返回html内容
html = ''
try:
# 利用urllib.request.urlopen()方法,发送请求,并将返回结果保存至response变量
# 下文对response对象的info()方法进行了输出,向读者展示了urllib.request.urlopen()方法具体返回的是什么
response = urllib.request.urlopen(request)
# 建议读者读至此处,转去阅读下文绿色底色文本,之后再返回本处
# 对返回的html文件(存储在response对象中)进行解码
html = response.read().decode('utf-8')
# 异常处理
except urllib.error.URLError as e:
if hasattr(e, 'code'):
print(e.code)
if hasattr(e, 'reason'):
print(e.reason)
# 返回解码处理好的html文件
return html
如果打印response对象,则会出现以下信息:
该信息表示response对象保存的内存地址。
该信息表示response对象的具体信息,包括创建日期(Sat, 01 Aug 2020 13:35:32 GMT),内容类型(txt / html)等内容。
其中Transfer-Encoding: chunked表示分块传输,可以通俗理解为,我们直接接收到的html文件因分块传输,被加密了,我们需要解码才能使用,我们可以用print(response.read())命令打印以下看看被加密的html文件长什么样子:
使用response.read().decode()方法解码后(部分)信息如下:
仔细观察上述两幅图片展示的信息(html文件解码前、html文件解码后)对比发现,除了解码后的文件,将换行符\n转义为换行操作外,还将十六进制代码按照UTF-8规则:
\xe8\xb1\x86\xe7\x93\xa3\xe7\x94\xb5\xe5\xbd\xb1 Top 250
解码成:
豆瓣电影 Top 250
可见解码操作是很重要的,只有解码后,才能正常显示我们想要的信息。
def getData(baseurl): # 爬取网页,用于获取数据
# 创建一个空列表,用于接受数据
datalist = []
for i in range(0, 10): # 循环10次,代表爬取10个网页
# 根据上文分析的10个页面URL之间的联系,生成URL
url = baseurl + str(i * 25)
# 保存获取到的网页源码
html = askURL(url)
# 使用BeautifulSoup()方法逐一解析爬取的网页,输入参数为:网页源码和选择使用的解析器
soup = BeautifulSoup(html, 'html.parser')
# 建议读者转去阅读下文中绿色底色文本,之后再返回阅读此处
# 遍历每一个属性为item的div标签
for item in soup.find_all('div', class_="item"): # 在网页源码中查找符合要求的标签(是一个div标签,且属性为item),形成列表
# 在本例中,item遍历的第一个标签为:(即在第一次循环,item等于如下)
# 请转至下方标黄字样处阅读
# 创建一个空列表,保存一部电影的所有信息
data = []
# 将查询到的html标签,转换为字符串格式
item = str(item)
# 通过正则表达式查找,关于re.find_all()方法的理解,可以参考下文中标黄字样处
# 获取影片链接
link = re.findall(findlink, item)[0]
# append()方法为向列表添加内容
data.append(link) # 添加链接
# 获取图片链接
ImgSrc = re.findall(findImgSrc,item)[0]
data.append(ImgSrc) # 添加图片
titles = re.findall(findTitle,item) # 片名可能有好几个
if(len(titles) == 2):
ctitle = titles[0] # 中文名
data.append(ctitle)
otitle = titles[1].replace('/','') # 去掉无关符号
data.append(otitle) # 添加外文名
else:
data.append(titles[0])
data.append(' ') # 外文名留空
# 添加评分
rating = re.findall(findrating,item)[0]
data.append(rating)
# 添加评价人数
judgeNum = re.findall(findJudge,item)[0]
data.append(judgeNum)
# 添加概述,概述有可能为空
inq = re.findall(findInq,item)
if len(inq) != 0:
inq = inq[0].replace('。','') # 去掉句号
data.append(inq)
else:
data.append(' ')
bd = re.findall(findBd,item)[0]
bd = re.sub('
(\s+?)',' ',bd) # 去掉
bd = re.sub('/',' ',bd)
data.append(bd.strip()) # 去掉前后空格
datalist.append(data) # 把处理好的一部电影信息放入datalist
return datalist
在上文中我们探讨了解码后的文件具体是什么样子,现在我们来看一下,经过BeautifulSoup()方法解析后的html文件具体是什么样。使用print(BeautifulSoup(html, ‘html.parser’))命令查看:
经对比,BeautifulSoup()方法解析后的html文件与解码后的文件几乎一模一样,那么BeautifulSoup()方法具体做了什么呢?
其实,BeautifulSoup()方法通过分析html文件,在内存中创建了一个又一个的类和对象,并将这些实例化的对象按照树的数据结构排列。
例如:创建
link = re.findall(findlink, item)[0]
参数"findlink",本文还未给出定义(因需要将findlink参数定义为全局变量,故不能定义在getData()函数中),现将定义给出:
findlink = re.compile(r'')
# re.compile()方法用于创造正则表达式对象
# 该正则表达式的意思为:一个字符串,开头一定得是:
因此,在本例中,link为item中第一个符合要求的字符串,即https://movie.douban.com/subject/1292052/,也就是对应的第一个影片的链接。
本文因篇幅有限,不过多介绍正则表达式的详细内容,读者只需了解通过正则表达式,我们可以找寻到符合特定规则的字符串
# 影片链接
findlink = re.compile(r'') # re.compile()方法用于创造正则表达式对象
# 影片图片链接
findImgSrc = re.compile(r', re.S) # re.s参数让换行符包含在字符中
# 影片片名
findTitle = re.compile(r'(.*)')
# 影片评分
findrating = re.compile(r' ')
# 评价人数
findJudge = re.compile(r'(\d*)人评价')
# 找到概况
findInq = re.compile(r'(.*)')
# 找到影片相关内容
findBd = re.compile(r'(.*?)
', re.S)
def saveData(datalist,savepath): # 参数为需要保存到excel中的数据、保存路径
# xlwt.Workbook()方法为新建一个excel文件
book = xlwt.Workbook(encoding='utf-8',style_compression=0)
# add_sheet()方法为在新建的excel文件中添加一个sheet
sheet = book.add_sheet('豆瓣电影Top250', cell_overwrite_ok=True)
# 将列名保存到一个元组中
col = ('电影详情链接','图片链接','影片中文名','影片外文名','评分','评价人数','概况','相关信息')
# 向sheet表写入数据:写入列名
for i in range(0,8):
# write()方法中的第一个参数为行号,第二个参数为列号,第三个参数为保存的数据
sheet.write(0,i,col[i])
# 保存电影信息
for i in range(0,250):
h = i +1
# 输出记录下正在保存第几条信息
print('第 %d 条' % h)
data = datalist[i]
for j in range(0,8):
sheet.write(i + 1,j,data[j])
book.save(savepath)
def main():
baseurl = 'https://movie.douban.com/top250?start=' # 定义网页URL
# 1. 爬取网页+解析数据
datalist = getData(baseurl)
# 2. 保存数据
savepath = '豆瓣电影Top250.xls'
saveData(datalist,savepath) # 定义将数据保存到当前文件夹下
到此,便完成了我们爬取豆瓣Top250名电影信息的项目。
作者在完成本项目时,参考了以下内容,在此对编写以下内容的作者表示感谢:
- B站IT私塾Python爬虫技术课程
- URL转换成IP的过程:https://blog.csdn.net/qq_31869107/article/details/89363996