1. 基础知识
1.1. 网址信息
分页的网址特点:在分页中都有属于自己的特征属性。
- 如豆瓣中的:https://movie.douban.com/top250 是有属性的。
- 属性一般在网页的’?'后面,如上面的网页就有’start’属性,表示当页是从第几条电影数据之后进行的查看。
- 例:如果要查看第26~50条的数据,则使用https://movie.douban.com/top250?start=25
1.2. 获取网址信息
通过F12的开发者工具,可以找到获取元素的操作。
网址信息中还有随着网页链接一起发过去的信息:User-Agent、Cookie等。(Header中就有我们通过网址向服务器中发出的各种消息,可以在Network中找发送的网址查看)
- Cookie:操作Cookie才能得到需要登录之后才能爬取的信息。
2. 获取数据
2.1. 获取元素层级
通过F12开发者工具得到元素后,会自动显示出当前元素的各层信息。
3. 引入库
需要用到的库:
- bs4 # 网页解析,获取数据
- re # 正则表达式,进行文字匹配
- urllib # 制定URL,获取网页数据
- xlwt # 进行Excel操作
- sqlite3 # 进行SQLite数据库操作
3.1. urllib库
3.1.1. 获取信息
urllib.request.urlopen() # 可以用于打开一个网址并返回一个网址对象
假设对象为response,使用read()方法可以获取网址的源代码信息
# 例
response.read()
再使用decode(“utf-8”)方法可以对read()读出的进行utf-8编码解析(其中编码可以改成别的,可以解除里面把所有转义字符暴露的问题)
# 例
response.read().decode("utf-8")
3.1.2. urllib库实例
# -*- codeing = utf-8 -*-
# @Time : 2021/4/3 21:39
# @Auther : hanhan
# @File : urllib.py
# @Software : PyCharm
import urllib.request
import urllib.parse
# 基本信息
url = "" # 输入爬取网址
# url = "http://httpbin.org/post"
# url = "http://www.baidu.com/"
url = "http://www.douban.com/"
headers = { # 用于伪装的信息
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36"
}
# data = bytes(urllib.parse.urlencode({"name": "eric"}), encoding="utf-8")
req = urllib.request.Request(url = url, headers = headers) # 伪装后的申请信息
if __name__ == '__main__':
reponse = urllib.request.urlopen(req)
print(reponse.read().decode("utf-8"))
3.2. bs4库
[假设bs为BeautifulSoup的对象]
3.2.1. 组成部分
-
Tag: 标签及其内容(可以拿到找到的第一个标签内容)。
-
可以用bs.<标签名>索引得到第一个该标签及内容。
# 例 print(bs.a) # 输出:<a>hanhan</a>
-
-
NavigableString: 标签里的内容(字符串)。
-
若只要内容则可以用bs.<标签名>.string得到。
# 例 print(bs.a.string) # 输出:hanhan
-
也可以获得标签的属性字典:bs.<标签名>.attrs。
# 例 print(bs.a) # 输出:['class': 'hanhan', 'name': 'hanhan']
-
-
BeautifulSoup: 表示整个文档。
-
Comment: 是一个特殊的NavigableString,输出的内容不包含注释符号。
3.2.2. 文档的遍历
-
使用content可以得到当前标签下的所有标签内容组成的列表:
# 例 print(bs.head.contents) # 输出:[<meta/>, <meta/>]
-
更多操作内容可以搜索函数文档。
3.2.3. 文档的搜索
-
find_all()方法
- 字符串过滤:查找与输入字符串完全匹配的内容(查询所有标签,并储存为列表)。
# 例 print(bs.find_all("a")) #查询文档中所有的a标签
- 正则表达式: 使用search()函数,采用正则表达式搜索。
# 例 print(bs.find_all(re.compile("a"))) #输出所有包含a的内容
- 方法:传入一个方法,根据函数要求进行搜索。
# 例 def name_is_exists(tag): return tag.has_attr("name") print(bs.find_all(name_is_exists)) #打印出所有包含有name属性的标签 #因为所有有name属性的标签才会被函数选出并返回
-
kwargs 参数
-
可以控制其中的某一属性进行选取(呈列表输出)
# 例1 print(bs.find_all(id = "head")) # 输出id = "head"的标签全内容 # 例2 print(bs.find_all(class_ = True)) # 输出所有存在class的标签全内容(要加下划线是因为class本身是一个关键字。
-
-
text 参数
-
可以用文本参数查找内容
# 例1 print(bs.find_all(text = ["han", "hanhan"])) # 输出内容为han和hanhan的文本 # 例2 print(bs.find_all(text = re.comile("\d"))) # 输入内容包含数字的所有文本
-
-
limit 参数
-
可以限定获取的个数
# 例 print(bs.find_all("a", limit = 3)) # 输出限定为最多三个
-
-
css选择器(使用select函数查找)
-
使用标签、类名、ID等查找(和css的定义方式一样)
# 例1 print(bs.select("title")) # 输出title的标签列表 # 例2 print(bs.select(".mnav")) # 输出包含该类名的标签列表 # 例3 print(bs.select("#u1")) # 输出包含该ID的标签 # 例4 print(bs.select("a[class = 'bri']")) # 输出包含该属性的a标签 # 例5 print(bs.select("head > title")) # 通过标签的父子关系进行配对查找 # 例6 print(bs.select(".mnav ~ .bri")) # 通过兄弟节点进行查找
-
3.3. re库
正则表达式:对字符串的规定
3.3.1. 基本操作符
操作符 | 说明 | 实例 |
---|---|---|
. | 表示任何单个字符 | |
[ ] | 字符集,对单个字符给出取值范围 | [abc]表示a、b、c,[a-z]表示a到z单个字符 |
[^ ] | 非字符集,对单个字符给出排除范围 | [^abc]表示非a或b或c的单个字符 |
* | 前一个字符0次或无限次拓展 | abc* 表示 ab、abc、abcc、abccc等 |
+ | 前一个字符1次或无限次拓展 | abc+ 表示 abc、abcc、abccc等 |
? | 前一个字符0次或1次拓展 | abc? 表示 ab、abc |
| | 左右表达式任意一个 | abc | def 表示 abc、def |
操作符 | 说明 | 实例 |
---|---|---|
{m} | 扩展前一个字符m次 | ab{2}c表示abbc |
{m, n} | 扩展前一个字符m至n次 | ab{1, 2}c表示abc、abbc |
^ | 匹配字符串开头 | ^abc表示abc且在一个字符串的开头 |
$ | 匹配字符串结尾 | abc$表示abc且在一个字符串的结尾 |
( ) | 分组标记,内部只能使用 | 操作符 | (abc)表示abc,(abc | def)表示abc、def |
\d | 数字,等价于[0-9] | |
\w | 单词字符,等价于[A-Za-z0-9_] |
3.3.2. 主要功能函数
函数 | 说明 |
---|---|
re.search() | 在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象 |
re.match() | 在一个字符串的开始位置起匹配正则表达式,返回match对象 |
re.findall() | 搜索字符串 ,以列表类型返回全部能匹配的子串 |
re.split() | 将一个字符串按照正则表达式匹配结果进行分割,返回列表类型 |
re.finditer() | 搜索字符串,反会有个匹配结果的迭代类型,每个迭代元素是match对象 |
re.sub() | 在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串 |
3.3.3. 正则表达式的模式
修饰符 | 描述 |
---|---|
re.l | 使匹配对大小写不敏感 |
re.L | 做本地化识别(locate-aware)匹配 |
re.M | 多行匹配,影响^和$ |
re.S | 使.匹配包括换行在内的所有字符 |
re.U | 根据Unicode字符集解析字符。这个标志影响\w, \W, \b, \B |
re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解 |
3.3.4. 功能函数样例解析
-
re.compile(str)用于创建模式对象。
pat = re.compile("AA") # 创建模板
-
<对象名>.search(str)用于模式与字符串匹配(得到第一个匹配到的字符串)。
# 字符串验证 ans1 = pat.search("CBA") print(ans1) # 输出:None ans2 = pat.search("CBAAAABC") print(ans2) # 输出:<re.Match object; span=(2, 4), match='AA'>
-
re.search(str, str)用于模式匹配,前面为模板,后面为校验对象。
ans = re.search("asd", "Aasd") print(ans) # 输出:<re.Match object; span=(1, 4), match='asd'>
-
re.findall(str, str)同样用于模式匹配,前面为模板,后面为校验对象。不过是输出匹配到的字符串的列表。
ans1 = re.findall("a", "ASDaDFGAa") print(ans1) # 输出:['a', 'a'] ans2 = re.findall("[A-Z]", "ASDaDFGAa") print(ans2) # 输出:['A', 'S', 'D', 'D', 'F', 'G', 'A']
-
re.sub(str1, str2, str)用于字符串的替换:在str中用str2替换str1
(str1为正则表达式,其余为字符串)。
建议:在被比较字符串前加上r,防止转义字符对文本的影响。ans = re.sub("a+", "A*", r"abcdefaabcda") print(ans) # 输出:A*bcdefA*bcdA*
3.4 xlwt库
3.4.1 创建Excel表对象
基本的创建对象操作
workBook = xlwt.Workbook(encoding = "utf-8") # 创建文档对象
workSheet = workBook.add_sheet("sheet1") # 创建工作表
3.4.2 简单操作案例
写+保存
workSheet.write(0, 0, "hello") # (行,列,内容)
workBook.save(r"C:\Users\Asus\desktop\student.xls") # 设置保存路径并保存
打印九九乘法表
# -*- codeing = utf-8 -*-
# @Time : 2021/4/6 20:23
# @Auther : hanhan
# @File : xwltText.py
# @Software : PyCharm
import xlwt
workBook = xlwt.Workbook(encoding = "utf-8") # 创建文档对象
workSheet = workBook.add_sheet("sheet1") # 创建工作表
for i in range(1, 10):
for j in range(1, i + 1):
# workSheet.write(i, j,str(j) + " * " + str(i) + " = " + str(i * j)) # (行,列,内容)
workSheet.write(i, j, "%d * %d = %d"%(j, i, i * j)) # (行,列,内容)
workBook.save(r"C:\Users\Asus\desktop\student.xls") # 设置保存路径并保存
3.5 sqlite库
sqlite主要用来将数据存储到数据库,要用到pycharm专业版。
3.5.1 创建数据库
创建数据库操作很简单:直接用sqlite3中的connet函数创建。
import sqlite3
# 连接数据库
conn = sqlite3.connect("test.db") # 打开或创建数据库文件
print("Opened database successfully")
3.5.2 创建数据表
创建表的过程等同于用sql创建表的过程,不过在python中要用函数对sql命令进行解析。
import sqlite3
# 连接数据库
conn = sqlite3.connect("test.db") # 打开或创建数据库文件
print("Opened database successfully")
c = conn.cursor() # 获取对数据库进行操作的游标
# 创建数据表
sql = '''
create table company (
id int primary key not null,
name text not null,
age int not null,
address char(50),
salary real
);
'''
cursor = c.execute(sql) # 执行sql语句进行建表
conn.commit() # 提交数据库操作
conn.close() # 关闭数据库连接
print("Create table successfully")
3.5.3 插入数据
插入数据操作类似,但是注意一次只能插入一条数据,所以一个sql文本中只能有一条语句。
import sqlite3
# 连接数据库
conn = sqlite3.connect("test.db") # 打开或创建数据库文件
print("Opened database successfully")
c = conn.cursor() # 获取对数据库进行操作的游标
# 插入数据
sql1 = '''
insert into company(id, name, age, address, salary)
values(1, "hanhan1", 32, "北京", 15000);
'''
sql2 = '''
insert into company(id, name, age, address, salary)
values(2, "hanhan2", 30, "南京", 8000);
'''
# 执行sql语句插入两次语句
c.execute(sql1)
c.execute(sql2)
conn.commit() # 提交数据库操作
conn.close() # 关闭数据库连接
print("Insert successfully")
3.5.4 查询数据
查询数据对于execute函数有返回值,为一个元组,所以可以用打印方式返回。
import sqlite3
# 连接数据库
conn = sqlite3.connect("test.db") # 打开或创建数据库文件
print("Opened database successfully")
c = conn.cursor() # 获取对数据库进行操作的游标
# 查询数据
sql = "select id, name, address, salary from company"
cursor = c.execute(sql) # 执行sql语句并返回数据信息
# 打印列表
for row in cursor:
print("id = ", row[0])
print("name = ", row[1])
print("address = ", row[2])
print("salary = ", row[3], "\n")
conn.commit() # 提交数据库操作
conn.close() # 关闭数据库连接
print("Select successfully")
4. 综合案例
下面综合案例为爬取豆瓣排行榜列表信息。
代码采用数据库存储,Excel存储为注释内容。
# -*- codeing = utf-8 -*-
# @Time : 2021/4/1 0:22
# @Auther : hanhan
# @File : Main.py
# @Software : PyCharm
from bs4 import BeautifulSoup # 解析网页源代码库
import re # 正则表达式库
import urllib.request, urllib.error # 获取网页信息库
import xlwt # xls文件操作库
import sqlite3 # db文件操作库
# 操作函数
def main():
url = "https://movie.douban.com/top250?start=" # 爬取的基础网址
# savePathXls = "C:/Users/Asus/Desktop/doubanTop250.xls" # 保存的本地地址
# savePathXls = "./result.xls"
savePathDb = "C:/Users/Asus/Desktop/doubanTop250.db" # 保存的本地数据库地址
# savePathDb = "./doubanTop250.db"
# 爬取网页
datalist = GetData(url)
# 保存数据
# SaveDataXls(datalist, savePathXls)
SaveDataDb(datalist, savePathDb)
# 爬取网页(获取网页数据)函数
def GetData(url):
datalist = []
for i in range(0, 250, 25):
tmpUrl = url + str(i)
html = AskUrl(tmpUrl) # 获取每个网页的数据
# 解析数据
soup = BeautifulSoup(html, "html.parser")
for item in soup.find_all("div", class_ = "item"): # 提取所有符合要求的模块的代码
# print(item)
data = []
item = str(item) # 把网页源码转换成字符串
# 创建正则表达式对象用于获取想要的信息
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) # 查找相关信息
# 通过创建的正则表达式对象获得信息
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: # 中、外文标题
chineseTitle = titles[0]
data.append(chineseTitle)
foreignTitle = titles[1].replace("/", "")
data.append(foreignTitle)
else: # 标题可能只有一个
data.append(titles[0])
data.append(" ") # 第二个留空占位(因为要存入Excel或数据库)
rating = re.findall(findRating, item)[0]
data.append(rating) # 添加评分
judge = re.findall(findJudge, item)[0]
data.append(judge) # 添加评价人数
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) # 去掉<br/>
bd = re.sub('/', " ", bd) # 替换/
data.append(bd.strip()) # 添加相关信息
# 返回处理好的一项电影信息
print("Getting successfully...")
datalist.append(data)
print("\nGet infomation completed!!!\n\n\n")
return datalist
# 访问并获得网页源码
def AskUrl(url):
head = { # 用于伪装的信息
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36"
}
req = urllib.request.Request(url = url, headers = head) # 伪装后的申请信息
html = "" # 网页源代码
try:
response = urllib.request.urlopen(req)
html = response.read().decode("utf-8") # 用utf-8提取源码
# print(html)
except urllib.error.URLError as e:
if hasattr(e, "code"):
print(e.code)
if hasattr(e, "reason"):
print(e.reason)
return html
# 保存数据到xls文件
def SaveDataXls(datalist, savePathXls):
workBook = xlwt.Workbook(encoding="utf-8", style_compression = 0) # 创建文档对象
workSheet = workBook.add_sheet("doubanMovieTop250", cell_overwrite_ok = True) # 创建工作表
colHead = ("电影详情链接", "图片链接", "中文标题", "外文标题", "评分", "评价人数", "电影概述", "电影相关信息")
for i, u in enumerate(colHead): # 写入每一列的列名
workSheet.write(0, i, u)
for i, u in enumerate(datalist):
print("Saving successfully...")
for j in range(0, len(colHead)):
workSheet.write(i + 1, j, u[j])
workBook.save(savePathXls) # 设置保存路径并保存
print("\nSave infomation completed!!!\n\n\n")
# 保存数据到db文件
def SaveDataDb(datalist, savePathDb):
InitDb(savePathDb)
conn = sqlite3.connect(savePathDb) # 连接数据库
cur = conn.cursor() # 拿到操作数据库的游标
for data in datalist:
tmp = data.copy() # 将data列表全员放入tmp列表
for i in (0, 1, 2, 3, 6, 7): # 将字符串类型的变量加上双引号
tmp[i] = '"' + tmp[i] + '"'
sql = '''
insert into doubanTop250(info_link, pic_link, cname, ename, score, rated, introduction, info)
values(%s)
'''%",".join(tmp)
cur.execute(sql) # 执行sql语句
print("Saving successfully...")
conn.commit() # 提交数据库操作
conn.close() # 关闭数据库
print("\nSave infomation completed!!!\n\n\n")
# 创建数据库
def InitDb(savePathDb):
# 创建数据表
sql = '''
create table doubanTop250 (
id integer primary key autoincrement,
info_link text,
pic_link text,
cname varchar,
ename varchar,
score numeric,
rated numeric,
introduction text,
info text
)
'''
conn = sqlite3.connect(savePathDb)
print("Opened database successfully")
cur = conn.cursor() #获得一个操作数据库的游标
cur.execute(sql) # 执行语句,进行操作
conn.commit() # 提交数据库操作
conn.close() # 关闭数据库
print("Create table successfully")
if __name__ == '__main__':
main()
print("\nWork done!")