2022.07.28
学习资料:https://www.bilibili.com/video/BV12E411A7ZQ
Python爬虫基础部分
urllib库创建请求
get方式
使用 urllib.request.urlopen() 方法(需要引包),指定请求链接(需要加http://)和超时阈值等,然后使用 read() 方法将得到的返回信息打印,decode()方法用于进行格式化。
此处使用了 try/except进行错误捕获,主要是为了不在超时的情况下报一大堆错。
#使用get方式请求
import urllib.request
#超时处理
try:
timeout = 0.01
response = urllib.request.urlopen("http://www.baidu.com",timeout=timeout)
print(response.read().decode('utf-8')) #对获取到的网页进行utf-8解码
except urllib.error.URLError as e:
print("ERROR! Time out of "+str(timeout)+"s!")
post方式
使用post方式请求时,必须要附带一些表单信息,否则无法请求。此处使用 urllib.parse.urlencode() 方法并套一个 bytes 方法来创建附带的数据,附带的这些信息会附带在请求头中。
#使用post方式请求,post不能不传参数直接请求
import urllib.parse
#bytes:创建二进制数据,{"a":"b"}:键值对,其中a为key,b为value
data = bytes(urllib.parse.urlencode({"hello":"world"}),encoding="utf-8")
response = urllib.request.urlopen("http://httpbin.org/post",data=data)
print(response.read().decode("utf-8"))#解码可以顺便解码\n等,保证格式好看
创建请求头
对请求头进行伪装以避开某些网站的检测
使用 urllib.request.Request() 方法来使用创建好的请求头,其中可以指定url、附带信息、请求头、请求种类等
之后还是得用 urlopen 打开链接
#创建请求头
#url="http://httpbin.org/post"
url="http://www.douban.com"
#若不设置header,会被豆瓣认出来并说我是个茶壶
headers={
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36"
}
data=bytes(urllib.parse.urlencode({'name':'eric'}),encoding="utf-8")
req=urllib.request.Request(url=url,data=data,headers=headers,method="POST")
response=urllib.request.urlopen(req)
print(response.read().decode("utf-8"))
查看请求状态和响应头等
#看看请求状态和请求头
response = urllib.request.urlopen("https://www.baidu.com")
print(response.status)#200
#print(response.getheaders())#获取整个响应头
print(response.getheader("Date"))#注意headers和header
Beautiful Soup 4 库提取响应内容
from bs4 import BeautifulSoup
#创建BS实例
file = open("testPackages\douban.html","rb")#rb是读字节流
html=file.read().decode("utf-8")#打开html文件
bs=BeautifulSoup(html,"html.parser")#第一个参数是要解析的文件,第二个参数是解析方式
Tag
标签及其中的所有内容(包括其中的低级标签),会找到第一个符合的标签
print(bs.title)#如果输出bs.title的类型,则是Tag
print(bs.title.string)#只取标签中的文字内容
print(type(bs.title))
'''输出:
<title>豆瓣电影 Top 250</title>
豆瓣电影 Top 250
<class 'bs4.element.Tag'>
'''
NavigableString
获取标签里的内容(字符串)
print(bs.a.string)#
print(type(bs.a.string))
'''
登录/注册
<class 'bs4.element.NavigableString'>
'''
BeautifulSoup
获取整个文档
#print(bs.head.contents)#返回一个字典类型,内容是head下面的所有标签
print(bs.head.contents[1])#获取字典中的第一个元素(下标从0开始)
'''
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
(这是html中的第一个标签,上述字典中第0个元素是'\n')
'''
Comment
特殊的NavigableString,但是会自动过滤注释符号
print(bs.a.string)#如果其中有形如<!--abc-->的注释内容,其会被自动转换为abc
提取标签下的所有属性
print(bs.a.attrs)#字典类型
'''
{'href': 'https://accounts.douban.com/passport/login?source=movie', 'class': ['nav-login'], 'rel': ['nofollow']}
'''
文档的搜索
使用bs.find_all()或者bs.select()方法定位某些标签或者文字内容。
(1)以字符串匹配或者方法查找
理解:bs.find_all方法首先会获取到文档中的所有标签,然后根据方法传入的参数来筛选符合要求的标签,当参数是方法名时,将每一个标签传入方法,根据返回值判断这个标签是否可用。
#①字符串查找:返回与传入字符串完全匹配的内容
t_list = bs.find_all("span")#返回所有span标签(包括其子标签)
#②正则表达式:调用正则对象的search()方法来匹配内容
t_list = bs.find_all(re.compile("s"))#返回所有包含s的标签(包括其子标签)
#③函数:传入函数名,根据函数的内容来查找(类似C++和C#中sort函数排序时可以传入自定义比较函数)
def value_is_exist(tag):
return tag.has_attr("value")
t_list = bs.find_all(value_is_exist)#返回所有具有value属性的标签
(2)kwargs:查找某属性为某特定值的标签
参数中指定想要的标签中的属性内容
t_list = bs.find_all(value="unwatched")#找出所有value属性值为"unwatched"的标签
t_list = bs.find_all(class_=True)#找到所有拥有class属性的标签(class关键字为了消除歧义,需要使用class_来代替)
(3)text:查找符合某规则的标签中的文本
使用text时只会返回标签中的文本,不会返回整个标签
理解:一个标签的内容也被视作属性,其key为text,value为其中的内容
t_list = bs.find_all(text="豆瓣")#完全匹配,注意使用text时只会返回标签中的文本,不会返回整个标签
t_list = bs.find_all(text=["豆瓣","让子弹飞","后页"])#同时找多个,返回顺序是其在文档中本来的顺序
t_list = bs.find_all(text = re.compile("\d"))#使用正则表达式找出所有包含数字的文本
(4)limit参数:限定查找出的条目数量
t_list = bs.find_all(text=["豆瓣","让子弹飞","后页"],limit=3)#总之就是在原来的基础上截断了,与上面那个的结果相比只有其前三条
(5)css选择器:使用select()方法按照css选择器语法定位标签
t_list = bs.select("title")#所有的title
t_list = bs.select(".quote")#所有class为quote的标签
t_list = bs.select("#footer")
t_list = bs.select("a[href='https://movie.douban.com/tv/']")#找a标签中href属性为特定值的
t_list = bs.select("head > title")#找head下面的title
t_list = bs.select(".rating45-t ~ .rating_num")#找前一标签同级的后一标签
实例测试:提取豆瓣TOP250中电影的链接、片名等信息
以下是一部电影的信息
<div class="item">
<div class="pic">
<em class="">1</em>
<a href="https://movie.douban.com/subject/1292052/">
<img width="100" alt="肖申克的救赎" src="https://img2.doubanio.com/view/photo/s_ratio_poster/public/p480747492.webp" class="">
</a>
</div>
<div class="info">
<div class="hd">
<a href="https://movie.douban.com/subject/1292052/" class="">
<span class="title">肖申克的救赎</span>
<span class="title"> / The Shawshank Redemption</span>
<span class="other"> / 月黑高飞(港) / 刺激1995(台)</span>
</a>
<span class="playable">[可播放]</span>
</div>
<div class="bd">
<p class="">
导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /...<br>
1994 / 美国 / 犯罪 剧情
</p>
<div class="star">
<span class="rating5-t"></span>
<span class="rating_num" property="v:average">9.7</span>
<span property="v:best" content="10.0"></span>
<span>2649976人评价</span>
</div>
<p class="quote">
<span class="inq">希望让人自由。</span>
</p>
</div>
</div>
</div>
分别对需要的每一种类信息创建各自的正则表达式
#影片详情链接
linkPat = re.compile(r'<a href="(.*?)">')
#注意:findall()中,如果传入的正则表达式含有括号,则只会返回括号内的内容
def getData(baseurl):
datalist = []
for i in range(0,10):#共10页
url=baseurl+str(i*25)
html = askUrl(url)#访问链接获取到目标html文件
#2.逐一解析数据
bs = BeautifulSoup(html,"html.parser")
list = bs.find_all("div",class_="item")#解析发现,每个电影的相关信息都被class为item的div包裹
for item in list:
#print(item) #测试:获取全部电影的全部信息
data=[]
item = str(item)#re.findall要求传入字符串
#影片详情的链接
link = re.findall(linkPat ,item)[0]#如果会获取到多个,则可以只取第一个,否则会得到一个列表
print(link)
return datalist
解析完成后,将其存储到excel表格中
import xlwt
def SaveData(datalist, savePath):
print("saving...")
# 先创建好表格
workBook = xlwt.Workbook(encoding="utf-8") # 创建WorkBook对象
workSheet = workBook.add_sheet('豆瓣电影') # 添加工作表
# 添加表头
colName = ("影片详情链接", "影片图片链接", "中文片名", "外文片名", "分数", "评价人数", "一句话概括", "其他信息")
for i in range(0, 8):
workSheet.write(0, i, colName[i])
for i in range(0, 250):
for j in range(0, 8): # 隐患:最好将8修改为len(colName)
workSheet.write(i+1, j, datalist[i][j])
print("第%d个写入完成" % (i+1))
workBook.save(savePath)
return None
将数据存储到sqlite数据库中
注意:在创建sql语句时,字符串类型的数据必须加双引号,数值类型可加可不加。
def SaveData2DB(datalist, savePath):
Init_DB(savePath=savePath)
conn = sqlite3.connect(savePath)
cur = conn.cursor()
for data in datalist:
for index in range(len(data)):
data[index] = '"'+str(data[index])+'"'
sql = '''
insert into tblMovie250 (link,image,cTitle,oTitle,rating,judgeNum,generalization,info)
values (%s)
''' % ",".join(data)
cur.execute(sql)
conn.commit()
cur.close()
conn.close()
return None
异步爬取(以bilibili评论区内容为例)
某些网站的数据并不会一次性加载完(喜欢我AJAX吗),而是在用户做出某些特定操作后才会加载(如b站评论区,滚动条滑到底时才会继续加载下面的评论)。这种异步加载的实现逻辑是在触发需要加载的行为时,浏览器向AJAX发送请求,并由AJAX从服务器处获取到新加载的内容(JSON串)。在异步爬取中,我们只需要分析AJAX向服务器获取新内容时的请求链接,然后使用urllib来请求这个链接即可。
0.查看返回的JSON串格式


1.AskUrl
def AskUrl(url):
'''
根据访问链接获取对应的html页面
return: html页面
'''
# 若不设置header,会被豆瓣认出来并说我是个茶壶
head = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36"
}
req = urllib.request.Request(url=url, headers=head)
try:
response = urllib.request.urlopen(req)
html = response.read().decode("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
2.AskJsonByPage,按页号获取JSON串
将页数设置为1000可以看到能够获取一个JSON串,但是没有评论内容,可以将此作为循环结束标志。
baseUrlFront = "https://api.bilibili.com/x/v2/reply/main?csrf=d2f1171678cefe4350b2a43a023f6a4c&mode=3&next="
baseUrlAfter = "&oid=800272415&plat=1&type=1"
def AskJsonByPage(index):
'''
根据页号来访问评论区,页号从1开始
return: 获取到的JSON串
'''
url = baseUrlFront+str(index)+baseUrlAfter
# 分析JSON串得知,replies:"null"代表无内容
resp = AskUrl(url=url)
jsonStr = json.loads(resp)
if(jsonStr["data"]["replies"] is None):
return "Empty"
else:
commentLlist = jsonStr["data"]["replies"]
for item in commentLlist:
print(item["member"]["uname"] + " : " + item["content"]["message"])
return "Success"
3.循环爬取页面
注意:如果短时间请求次数过多,即使伪装了请求头也可能会被ban IP,一个解决方案是每爬一页停一秒。
此处设置循环上限为1000,防止死循环,如果某视频评论页数大于1000则再调整即可。
def main():
index = 1
while(AskJsonByPage(index=index) != "Empty" and index <= 1000):
index += 1
# AskJsonByPage(1000)
return None

其他内容
包括WordCloud词云、Flask后端服务组件、Sqlite数据库等,写在石墨文档了
- 本文链接:https://shinya754.github.io/2022/07/28/Python%E7%88%AC%E8%99%AB/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。