这个文档将带你进入爬虫的大门,爬虫是快速获取大量互联网信息的常用方法 这里只介绍基础的使用
import requests # request是一个python用于网络访问的包
url='https://movie.douban.com/subject/1292052/' #将我们需要获取信息的网址复制给“url”这个变量
url:统一资源定位符 这里我们直接理解为网址就 OK
res=requests.get(url) # 使用“get”方法来向服务器访问这个url,除了get还有post、put等方法
res
418 是服务器返回给我们的请求结果的代码,它来自一个超文本茶壶控制协议
注意事项:
在这个协议中幽默的指出了我们的访问已经被是被为爬虫了 我们需要在请求头(headers)中加入更多的信息来伪装自己的身份请求头(headers)可以理解为我们向服务器发起请求的门票,服务器可以通过请求头来验证我们的身份 一个请求头由名称(不区分大小写)后跟一个冒号“:”,冒号后跟具体的值(不带换行符)组成。 例如,我们找一个浏览器代理(User-Agent),将它添加到请求头中请求头详情
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66'}
好了,我们现在在请求头中添加了一个浏览器代理。再来试试访问这个 url 吧
res=requests.get(url,headers=headers) # 告诉服务器,把我要的东西给我,和第一次不太一样,这次我们加上了浏览器代理。res
服务器返回的代码已经变成 200,表示已经成功返回数据 我们将 get 返回的对象赋值给了 res 这个变量
res.content
**.content**返回二进制数据(数据使用二进制在网络中传输)
我们一般是看不懂的,需要使用**decode()方法进行解码**。
res_1=res.content.decode('utf8') #指定(默认)使用utf8这种编码方式进行解码res_1# 这样我们就看到我们熟悉的中文了。
我们发现,所有的网页的内容,都是放在标签里面的。我们只需要找到相应的标签,就能知道相应的内容。
例如:
<title>肖申克的救赎 (豆瓣)</title>
就是由 title 标签包裹了一串文字 因此,我们需要新的工具来帮助我们进行标签的查找。Beautifulsoup就是帮助我们查到标签和标签内内容的工具。
from bs4 import BeautifulSoup # 从bs4这个大包里,导入Beautifulsoup这个模块
soup=BeautifulSoup(res_1,'html.parser') # 将网页文本传给BeaytifulSoup类,并制定解析器为“html.parser”soup
beautifulsoup 基础使用
soup.find('h1')
在 h1 标签中还有span标签,可以进行链式调用
soup.find('h1').find('span',{'property':'v:itemreviewed'}).text
soup.find('h1').find('span',{'class':'year'}).text.replace('(','').replace(')','')
find 只会返回找到的第一个标签
soup.find('h1').find('span').text
find_all 会以列表形式返回找到的所有的标签
soup.find('h1').find_all('span')
url='https://movie.douban.com/subject/1292052/'headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66'}
res=requests.get(url,headers=headers)
soup=BeautifulSoup(res.content.decode('utf8'),'html.parser')
names=soup.find('span',{'class':'actor'}).find('span',{'class':'attrs'}).find_all('a')names
name=[]for i in names:name.append(i.text) #将names中的标签循环调用text属性存入name列表name
1.访问主页,获得所有的超链接
url='https://movie.douban.com/top250'headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66'}
req=requests.get(url,headers=headers)
req.text # text 便捷返回对content的解码内容(默认utf8)
soup=BeautifulSoup(req.text,'html.parser')soup
li_list=soup.find('ol').find_all('li') # 先拿到第一个ol标签再找到里面所有的li标签
web=[]for i in li_list:web.append(i.find('a').get('href'))web
2.循环访问列表中的链接,进一步获取数据
bd_list=[]for url in web:print('我在爬一部电影')req=requests.get(url,headers=header)soup=BeautifulSoup(req.content)bd_list.append(soup.find('span',{'property':'v:itemreviewed'}).text)print('高兴啊,爬完了一部电影')
bd_list
这部分将会引入爬虫中常用的正则表达式来解决网页标签不配对的问题
目的:爬取豆瓣 250
要求:爬取豆瓣 250 里的制片国家/地区,语言,上映时间这些信息
我们再来复习一下之前的爬虫操作流程:
- 准备相关的工具,导入相关的模块库
- 设置好代理(目前我们只需要用 headers 模拟浏览器)。准备好需要爬的网站的 url。
- 使用 BeautifulSoup 获取特定标签下的数据
用我们之前学过的知识,爬取豆瓣 TOP250 的网页内容:
import requestsfrom bs4 import BeautifulSoup
headers={'User-Agent':'MMozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0'}url='https://movie.douban.com/top250'
res=requests.get(url=url,headers=headers)
还是使用‘uft-8’解码我们获取的 response 里的内容
soup=BeautifulSoup(res.content,'html.parser')
soup
获取其中一部电影的网址:
new_url=soup.find('ol',class_='grid_view').find('a',class_='').get('href')new_url
res1 = requests.get(new_url,headers=headers)
soup1=BeautifulSoup(res1.content,'html.parser')soup1
s1=soup1.find('div',{'id':'info'})s1
在上一个结果的基础上再进行寻找:
s2=s1.find_all('span',{'class':'pl'})s2
发现直接使用 Beautifulsoup 无法提取出我们需要的信息。
为什么会出现这样的问题?如何去解决?
解决方法一:尝试把未知的问题转换成已知的问题
mystr='<span class="pl">制片国家/地区:</span> 美国<span class="pl">语言:</span>'
s4=BeautifulSoup(mystr,'html.parser')s4.find('span',class_='pl')
解决方法二:尝试使用新工具
先来看一个正则表达式的例子
import re
mystr='class="pl">制片国家/地区:</span> 美国<span class="pl">语言:<'
b=re.findall(r'</span> (.*)<span',mystr)b
['美国']
什么是正则表达式?
正则表达式并非一门专用语言,但也可以看作是一种语言,它可以让用户通过使用一系列普通字符和特殊字符构建能明确描述文本字符串的匹配模式。除了简单描述这些模式之外,正则表达式解释引擎通常可用于遍历匹配,并使用模式作为分隔符来将字符串解析为字符子串。
要使用正则表达式,首先需要导入 re 模块。 正则表达式有两种基本的文本匹配方法:
1.findall 2.search
import re #regrex
#case 1:匹配数字a='123'# b=re.findall(r'你的模式',你要查看的字符串)b=re.findall(r'[0123456789]',a)print(b)a='@#$FSF123'b=re.findall(r'[0-9]',a)print(b)
#case 2:匹配字母a='123'# b=re.findall(r'你的模式',你要查看的字符串)b=re.findall(r'[a-z]',a)print(b)a='@#$FSFf123'b=re.findall(r'[A-Za-z@]',a)print(b)#[]的作用,给定一个匹配的对象,[]内的内容就是你要匹配的字串的一个集合
def m(pattern,text):print(re.findall(pattern,text))
m(r'[A-Za-z#$&]','@#$FSF123')
note:在正则表达式中,有一些快捷方式
# [0-9]# [A-Za-z]# \d 表示的是任意的一个数字(digits)# \w 表示的是任意一个字母和数字等(word)# \s 表示的是一些空格,换行符这类的,并且在结果里显示出来# \b 表示的是长度为0的字符串,不在结果里显示。可以通俗的理解为,\b放前面,表示要找一个前面没有相邻字符的这么一个字串
m(r'[\d]','12345')m(r'[\w]','abc123anc')
a='1236#@$Sf34'b=re.findall(r'[\d]',a)print(b)a='abc123anc$#%#$%'b=re.findall(r'[A-Za-z]',a)print(b)
text='sjfksPython Python I am coming'm(r'\bPython',text)
re.search(r'Python\b',text)
text='sjfksPython Python I am coming'm(r'Python\b',text)
text='sjfksPython Python I am coming'm(r'\bPython\b',text)
def t(text,pattern):print(re.findall(pattern,text))
a='masterpython python sdkjflsj pythonsfsf'b=re.findall(r'python',a)print(b,'\n','-'*80)t(a,r'\spython')b=re.search(r'\spython',a)print(b,'\n','-'*80)t(a,r'\spython\s') # 记得我门之前说过, \s匹配空格,换行等t(a,r'python\s')t(a,r'\bpython')b=re.search(r'\bpython',a)print(b,'\n','-'*80)t(a,r'\bpython\b')t(a,r'python\b')
a='masterpython python sdkjflsj pythonsfsf'
re.search(r'python\b',a)
#{} 表示的是一个范围,里面可以是具体的数值,也可表示的是一个闭区间。# 这种方法总是优先匹配最长的,因为正则匹配使用的是贪心算法 。t('1234325692312',r'[\d]{2,3}?')t('1234325693',r'[\d]{1,4}')
text='honour3 honor4sfd s f honor5 yy good'
t(text,r'(honour)|(honor)')
t(text,r'honou?r[\d]')
#一种特殊符号 ? 表示可保留,也可表示不保留。因为正则匹配是贪心的,所以会尽可能保留完整的。#1.情况1:处理单个模式的匹配t('python3 python',r'python3?')t('honour honor',r'honou?r')#2.情况2:t('123$%#1233467',r'[\d]{1,6}')# 表面上,是非贪心算法,从区间的下限这个数量去查找。在实际过程中,永远只能是最短匹配
# . :可以匹配任何字符t('$%@#%@%AFDSF!@#!3123fsf___+++',r'.+')
# 另外两个特殊字符 * +# *表示重复0到n次# +表示重复1到n次t('$%@#%@%AFDSF!@#!3123fsf___+++',r'.*')t('$4%@4.4DFGFDGDFG4+4#%@%4AFD44%$4SF!@#!3123f4sf___+++',r'4.+?4')# 正则匹配是贪心的,总是希望找到的子串尽可能地长
b=re.findall(r'[\d]{2,3}?rr',a)b
b=re.findall(r'\%\%\£\£[0-9]{2,3}?',a)b
a='masterxiao master xxx'b=re.findall(r'\smaster\s',a)b
def t(a,pattern):temp=re.findall(pattern,a)print(temp)
a='masterxiao master xxx master abc'#\B是\b的取反。\b可以匹配长度为0的字符串#\B匹配长度不为0的字符串b=re.findall(r'\Bmaster\B',a)print(b)b=re.findall(r'\bmaster\b',a)print(b)b=re.findall(r'master',a)print(b)b=re.findall(r'master\b',a)print(b)a='masterxiaotmaster xxx master abc'b=re.findall(r'\bmaster\b',a)print(b)b=re.findall(r'master\b',a)print(b)b=re.findall(r'\Bmaster',a)print(b)b=re.search(r'\Bmaster',a)print(b)
# ^词 表示以这个词开头# 词$ 表示以这个词结尾a='this code in python'b=re.findall(r'^python',a)print(b)t(a,r'python$')
t('this is python 3',r'python$')t('this is python',r'python')
#特殊字符 . 可以匹配任何字符t('this is my test%',r'test.')#可选字符 ? 可以存在或者不存在t('this is test tet',r'tes?t')
#{n}表示重复n次,可以是一个区间。采用的是贪婪匹配模式t('find all m&$#',r'[\w]{2,3}')#使用?可以避免使用贪婪匹配模式,但是往往结果只能匹配成最短的字串t('find all m&$#',r'[\w]{2,3}?')#使用+表示重复1至多个特定的字符 (速写)t('find all m&$#',r'[\w]+')#使用*表示重复0至多个特定的字符 (速写)t('find all m#@#',r'[\w]*')
text='汪峰唱了:《我要怒放的生命》。真的非常好听,尽管他经常破音。小李说,我不同意,王菲的《人间》更好听,还不破音。王小小说,你这都比不过迪玛希,他的高音无人能敌,一首《SOS》响彻天际。'text
t(text,r'《(.+?)》')
text='+++{}...[]'
import randomls=['sfsf','fufo23@#','2432fSDF@#$服务']new_string=text+random.choice(ls)print(new_string)
t(new_string,r'[\+]')
例题 1:使用正则表达式解析 IP 地址。Ip 地址往往都是(1-255).(0-255).(0-255).(0-255)的格式。
a='sdf fjiwer jweiclc swf172A131.0.123sdfsdf2sfsidfi@#
a='sdf fjiwer jweiclc swf172.131.0.123sdfsdf2sfsidfi@#$@#$WEF@178.131.12.123#@$FWFSD'
t(a,r'[\d]{1,3}.[\d]{1,3}.[\d]{1,3}.[\d]{1,3}')
t('sdf fjiwer jweiclc swf172.131.0.123sdfsdf2sfsidfi@#$@#$WEF@178.131.12.123#@$FWFSD',r'[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}')
a='sdf fjiwer jweiclc swf172.131.0.123sdfsdf2s't(a,r'[\d]{3}\.[\d]{3}\.[\d]{0,3}\.[\d]{3}')
例题 2:使用正则表达式提取电话号码。 非金属粉红色的哦覅我和哦¥%DSFSF2138675509kjshlfj khs 就十分喀麦隆 (213)8675509 分数马克思,马上。 @#¥@#!3 213.867.5509 你就安分,啊是的 iwe ¥……#¥%#@发 (213)-867-5509 发顺丰鸟看计划九十六来看 9234 鹅 1(213)867-5509 十分将人吗 +1-213-867-5509 发十分将破佳木斯普适
假如电话号码有以下几种形式: (213)8675509 213.867.5509 (213)-867-5509 1(213)867-5509 +1-213-867-5509, 把电话号码从文本里提取出来
text='非金属粉红色的哦覅我和哦¥%DSFSF2138675509kjshlfj khs 就十分喀麦隆(213)8675509分数马克思,马上。 @#¥@#!3 213.867.5509你就安分,啊是的iwe ¥……#¥%#@发(213)-867-5509 发顺丰鸟看计划九十六来看9234鹅1(213)867-5509 十分将人吗 +1-213-867-5509发十分将破佳木斯普适'text
pattern=r'[+]?[\d]?[-]?[(]?[\d]{3}[)]?[.-]?[\d]{3}[.-]?[\d]{4}'t(text,pattern)
pattern=r'\+?1?[-.]?(\(?[\d]{3}\)?)[-.]?([\d]{3})[-.]?([\d]{4})'pattern2=r'(\+?1?)[-.]?(\(?[\d]{3}\)?)[-.]?([\d]{3})[-.]?([\d]{4})'pattern3=r'\+?1?[-.]?\(?([\d]{3})\)?[-.]?([\d]{3})[-.]?([\d]{4})'ls=['2138675509''(213)8675509''213.867.5509''(213)-867-5509''1(213)867-5509''+1-213-867-5509']for i in ls:t(i,pattern)for i in ls:t(i,pattern2)for i in ls:t(i,pattern3)
ls='2138675509¥%¥#FsF@#fsdf(213)8675509fsfsdf23$@213.867.5509fksuus#@(213)-867-5509sdfkaiis#@#^fsffs1(213)867-5509$#Fdfqf+1-213-867-5509'print(ls)
a='<汪涵>唱了<怒放的生命>'b=re.search(r'<(.*?)>.*<(.*?)>',a)print(b)print(b.group(1))print(b.group(2))
b=re.findall(r'\+?1?[-.]?\(?[\d]{3}\)?[-.]?[\d]{3}[-.]?[\d]{4}','2138675509¥%¥#FsF@#fsdf(213)8675509fsfsdf23$@213.867.5509fksuus#@(213)-867-5509sdfkaiis#@#^fsffs1(213)867-5509$#Fdfqf+1-213-867-5509')print(b)
# 第一步,找共性: 213 某些符号 867 某些符号 5509 r'\+?1?[-]?\(?[\d]{3}\)?[-.]?[\d]{3}[-.]?[\d]{4}'# 第二步 找不同 有的前面有1/+1#问题 :什么时候需要加\转义? 就是当这些符号本身就作为正则表达式的特殊符号的时候
r'\+?1?[-.]?\(?[\d]{3}\)?[-.]?[\d]{3}[-.]?[\d]{4}'
回到问题本身:如何抓取豆瓣 250 中的制片国家/地区以及语言。
a='<span class="pl">制片国家/地区:</span> 美国<br><span class="pl">语言:</span> 英语<br>'#b=re.search(r'(</span>)(.+)(<br>)',a)b=re.findall(r'</span>(\s.*?)<br>',a)b#for i in b:# c=re.search(r'(</span>)(.*)(<br>)',i)# print(c.group(2))#b.group(2).replace(' ','')#t(a,r'(</span>)(.*?)(<br>)')