爬虫基本概念
何为网络爬虫
所谓爬虫(网络机器人),就是编写程序,模拟浏览器发送网络请求,接收请求响应,按照一定的规则,自动地抓取互联网信息的程序。
爬虫的意义
互联网大数据时代,面对海量的数据,如何获取到有效的信息显得十分关键。而网络爬虫为我们提供了一种筛选相关信息并分析整合的手段,是一种强有力的工具。
爬虫准备
做爬虫,其实很多语言都可,例如Python,PHP,Java等,当然其中最简单的是Python了,并且功能也比较齐全。
在Python中,一些常用的库包括:
from bs4 import BeautifulSoup # 网页解析,获取数据
import re # 正则表达式,进行文字匹配
import urllib.request, urllib.error # 指定URL,获取网页数据
import xlwt # 进行excel操作
import sqlite3 # 进行SQLite数据库操作
一个入门案例
爬取豆瓣评分电影Top250:https://movie.douban.com/top250
爬取内容包括:
- 电影详情链接
- 图片链接
- 影片中文名
- 影片外国名
- 评分
- 评价数
- 概况
- 相关信息
源代码
# 开头设置编码为utf-8,写在开头,防止乱码问题
# -*- codeing = utf-8 -*-
# 导入用到的库
from bs4 import BeautifulSoup # 网页解析,获取数据
import re # 正则表达式,进行文字匹配`
import urllib.request, urllib.error # 制定URL,获取网页数据
import xlwt # 进行excel操作
#import sqlite3 # 进行SQLite数据库操作
# 逐一解析数据:使用正则表达式,用来筛选、匹配信息
findLink = re.compile(r'<a href="(.*?)">') # 创建正则表达式对象,标售规则 影片详情链接的规则
findImgSrc = re.compile(r'<img.*src="(.*?)"', re.S)
findTitle = re.compile(r'<span class="title">(.*)</span>')
findRating = re.compile(r'<span class="rating_num" property="v:average">(.*)</span>')
findJudge = re.compile(r'<span>(\d*)人评价</span>')
findInq = re.compile(r'<span class="inq">(.*)</span>')
findBd = re.compile(r'<p class="">(.*?)</p>', re.S)
# 大体流程分三步走:
# 1. 爬取网页
# 2. 逐一解析数据
# 3. 保存网页
def main():
# baseurl指定了我们要爬取的网页地址
baseurl = "https://movie.douban.com/top250?start=" # 要爬取的网页链接
# 1.爬取网页
datalist = getData(baseurl)
savepath = "豆瓣电影Top250.xls" #当前目录新建XLS,存储进去
# dbpath = "movie.db" #当前目录新建数据库,存储进去
# 3.保存数据
saveData(datalist,savepath) # 保存到xls表和sqlite数据库,2种存储方式可以只选择一种,这里选择前者,故保存到数据库代码全部注释掉了
# saveData2DB(datalist,dbpath)
# 函数功能:爬取网页
def getData(baseurl):
datalist = [] # 用来存储爬取的网页信息,经过解析后符合要求的数据存放于此
# 由于豆瓣电影评分Top250,每一个页面只显示25个,所以需要访问页面10次
for i in range(0, 10): # 调用获取页面信息的函数,10次
url = baseurl + str(i * 25) # 根据i取值的不同,得到不同的url(仅仅start后面的参数不同而已)
html = askURL(url) # askURL()是请求网页的主体方法,保存获取到的网页源码
# 2. 逐一解析数据
soup = BeautifulSoup(html, "html.parser")
for item in soup.find_all('div', class_="item"): # 查找符合要求的字符串
data = [] # 保存一部电影所有信息
item = str(item)
link = re.findall(findLink, item)[0] # 通过正则表达式查找
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('<br(\s+)?/>(\s+)?', "", bd)
bd = re.sub('/', "", bd)
data.append(bd.strip())
datalist.append(data)
return datalist
# 函数功能:向网页发起请求,得到指定一个URL的网页内容
def askURL(url):
# 模拟浏览器头部信息,向豆瓣服务器发送消息,即伪装为浏览器与服务器的通信(否则会被服务器识破为爬虫而出错)
head = {
"User-Agent": "Mozilla / 5.0(Windows NT 10.0; Win64; x64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 80.0.3987.122 Safari / 537.36"
}
# 用户代理,表示告诉豆瓣服务器,我们是什么类型的机器、浏览器(本质上是告诉浏览器,我们可以接收什么水平的文件内容)
request = urllib.request.Request(url, headers=head)
html = ""
try:
response = urllib.request.urlopen(request)
# 下面读取网页内容,设置编码为utf-8,防止乱码
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)
return html
# 保存数据到表格
def saveData(datalist,savepath):
print("save.......")
book = xlwt.Workbook(encoding="utf-8",style_compression=0) #创建workbook对象
sheet = book.add_sheet('豆瓣电影Top250', cell_overwrite_ok=True) #创建工作表
col = ("电影详情链接","图片链接","影片中文名","影片外国名","评分","评价数","概况","相关信息")
for i in range(0,8):
sheet.write(0,i,col[i]) #列名
for i in range(0,250):
# print("第%d条" %(i+1)) #输出语句,用来测试
data = datalist[i]
for j in range(0,8):
sheet.write(i+1,j,data[j]) #数据
book.save(savepath) #保存
# def saveData2DB(datalist,dbpath):
# init_db(dbpath)
# conn = sqlite3.connect(dbpath)
# cur = conn.cursor()
# for data in datalist:
# for index in range(len(data)):
# if index == 4 or index == 5:
# continue
# data[index] = '"'+data[index]+'"'
# sql = '''
# insert into movie250(
# info_link,pic_link,cname,ename,score,rated,instroduction,info)
# values (%s)'''%",".join(data)
# # print(sql) #输出查询语句,用来测试
# cur.execute(sql)
# conn.commit()
# cur.close
# conn.close()
# def init_db(dbpath):
# sql = '''
# create table movie250(
# id integer primary key autoincrement,
# info_link text,
# pic_link text,
# cname varchar,
# ename varchar ,
# score numeric,
# rated numeric,
# instroduction text,
# info text
# )
#
#
# ''' #创建数据表
# conn = sqlite3.connect(dbpath)
# cursor = conn.cursor()
# cursor.execute(sql)
# conn.commit()
# conn.close()
# 保存数据到数据库
if __name__ == "__main__": # 当程序执行时
# 调用函数
main()
# init_db("movietest.db")
print("爬取完毕!")
代码解析
具体的代码解析已经写在了对应代码的注释,上述爬虫代码的总体思路分三步:
- 爬取网页
- 逐一解析数据
- 保存数据
爬虫预备知识
Web请求过程
- 服务器渲染:在服务器那边直接把数据和html整合在一起,统一返回给浏览器,特点是在页面源代码中能够看到数据。
- 客户端渲染:第一次请求只要一个html骨架,第二次请求拿到数据,进行数据展示。特点是在页面源代码中,看不到数据。
HTTP协议
什么是协议
所谓协议,就是两台计算机之间为了能够流畅地进行沟通而设置的一个君子协定,常见的协议包括TCP/IP,SOAP协议,HTTP协议,SMTP协议等等……
HTTP协议,即超文本传输协议,是用于从万维网服务器传输超文本到本地浏览器的传送协议。
HTTP协议将一条消息分为三大块内容,无论是请求还是响应都是三部分:
请求:
- 请求行:请求方式(get/post),请求url地址,协议
- 请求头:放置一些服务器要使用的附加信息
- 请求体:放置一些请求参数
响应:
- 状态行:协议,状态码
- 响应头:放一些客户端要使用的附加信息
- 响应体:服务器返回的真正客户端要使用的内容(HTML,json等)
请求头中最常见的一些重要内容包括:
- User-Agent:请求载体的身份标识(用什么发送的请求)
- Referer:防盗链
- cookie:本地字符串数据信息(用户登录信息,反爬的token)
响应头中的一些重要内容:
- cookie:本地字符串数据信息
- 各种神奇的莫名其妙的字符串
Requests库
request库是一个Python网络请求相关的第三方库,常用于Python爬虫。
入门实例1:爬取网络搜索页面
import requests
url = 'https://baike.baidu.com/item/%E5%91%A8%E6%9D%B0%E4%BC%A6/129156'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
}
# 浏览器地址栏中的内容,均以get方式进行请求
resp = requests.get(url, headers=headers)
print(resp.text)
这里加入headers请求头的原因,在于模拟浏览器向服务器发起get请求。否则,服务器就会识别出来,并不是浏览器在向它发起get请求,而是一个爬虫程序,就会出现错误。
request.get(url, params, …)方法,接收的参数包括
- url:地址
- params:参数
它将发起get请求,返回一个对象(response),包括的信息:
- text,即页面内容
- status_code,即响应状态码
- encoding,即响应的编码方式
正则表达式
使用元字符对字符串进行匹配,得到匹配结果。
Python中的re模块,提供了很多基于正则的函数,包括
- findall()
- finditer()
- search()
- compile()预加载等
import re
# findall: 匹配字符串中所有的符合正则表达式的内容,返回一个列表
all = re.findall(r"\d+", "中国移动10086,中国联通10010")
print(all)
# finditer:匹配字符串中所有的内容(返回一个迭代器),从迭代器中拿到内容需要.group()
it = re.finditer(r"\d+", "中国移动10086,中国联通10010")
for i in it:
print(i.group())
# search,找到一个匹配结果就返回一个对象,返回的结果是match对象,拿数据需要.group()
s = re.search(r"\d+", "中国移动10086,中国联通10010")
print(s.group())
# 预加载正则表达式,使提高速度
obj = re.compile(r"\d+")
# 接下来直接使用obj来调用re模块中的函数
ret = obj.finditer("中国移动10086,中国联通10010")
for it in ret:
print(it.group())
抓取指定内容
import re
s = """<div class="jay"><span id="1">过秦论</span></div>
<div class="jj"><span id="2">茅屋为秋风所破歌</span></div>
<div class="jolin"><span id="3">陈情表</span></div>
<div class="sylar"><span id="4">伶官传序</span></div>
<div class="tory"><span id="5">记承天寺夜游</span></div>"""
obj = re.compile(r'<div class=".*?"><span id="\d+">.*?</span></div>', re.S)
result = obj.finditer(s)
for it in result:
print(it.group())
现在,我要提取出过秦论、茅屋为秋风所破歌等文本信息,应该怎么办?
import re
s = """<div class="jay"><span id="1">过秦论</span></div>
<div class="jj"><span id="2">茅屋为秋风所破歌</span></div>
<div class="jolin"><span id="3">陈情表</span></div>
<div class="sylar"><span id="4">伶官传序</span></div>
<div class="tory"><span id="5">记承天寺夜游</span></div>"""
# 注意写法,.*?处的匹配结果存放在了wahaha中
obj = re.compile(r'<div class=".*?"><span id="(?P<id>\d+)">(?P<wahaha>.*?)</span></div>', re.S)
result = obj.finditer(s)
for it in result:
# 表示从wahaha中拿到匹配结果
print(it.group("wahaha"))
print(it.group("id"))
即,(?P