勤奋好学的小王计划用三天的时间学会基础的爬虫啦~加油加油
先大致制定一下学习计划8~
- 学会使用Requests库(自动爬html页面,自动网络请求提交)
- 了解网络爬虫排除标准robots.txt
- 学会解析html页面,要用beautiful soup
- 完成一至两个小项目实战
- 学会用正则表达式提取页面的关键信息
- 学会使用scrapy网络爬虫框架
目录
Requests库
第一步就是下载安装requests库,不过我好像之前已经安装过辣
然后去python自带的IDLE里面看看能不能用吧
没问题的!
requests库的七个主要方法:
其中,get和head是客户从服务器上获取资源;put、post、patch、delete是客户往服务器上面放资源!
1.requests.get()
最简单的获取网页内容的形式 —— r = requests.get(url)
这里的requests.get(url)就构建了一个Requests对象,它向服务器请求资源
r就是存取返回值,就是一个包含服务器资源的Response对象
下面就亲自试一下Response对象的属性吧~
>>> import requests
>>> r = requests.get("http://www.swu.edu.cn/")
>>> r.status_code
200
>>> r.text
'\r\n\r\n\r\n\r\n<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r\n<html xmlns="http://www.w3.org/1999/xhtml">\r\n<head>\r\n<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />\r\n<title>西南大学</title>\r\n<link rel="shortcut icon" type="image/x-icon" href="http://www.swu.edu.cn/images/swu.ico" media="screen"
#太多了,我就省略了
>>> r.encoding
'gb2312'
>>> r.apparent_encoding
'GB2312'
>>> r.content
b'\r\n\r\n\r\n\r\n<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r\n<html xmlns="http://www.w3.org/1999/xhtml">\r\n<head>\r\n<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />\r\n<title>\xce\xf7\xc4\xcf\xb4\xf3\xd1\xa7</title>\r\n<link rel="shortcut icon" type="image/x-icon" href="http://www.swu.edu.cn/images/swu.ico" media="screen" />
#太多了,我就省略了
试了一下之后我的感觉就是哈,提取出来的东西怎么感觉就和直接查看网页源代码里面的东西一样啊,目前感觉没有什么很腻害的,此外,去爬了一下其他网站,有些网站不能显示text内容,可能是因为权限吧。
关于encoding和apparent_encoding
encoding其实就是直接去网页的Head里面找到charset的属性,如果网页没有设置这个属性,好像是有一个默认值。
apparent_encoding就是request根据网页的响应内容去分析出来的编码方式了
关于text和content
text就是直接显示网页响应的内容吧,然后content是显示的二进制格式,这个我倒没什么特殊的感觉,从刚刚的实验中,很明显的就是text里面有中文,content里面没有中文,仔细看,text里面中文的部分,在content里面好像被\xce\xf7\xc4\xcf\xb4\xf3\xd1\xa7这种类型的东东代替了,具体我也不是很清楚
2.requests.head()
其实就是访问网页头部这一块的信息辣~
3.requests.post()
向页面提交了mydata字典里面的数据,但是在p.text里面都没有获取到我传入的数据,按道理说,url会把我的字典自动编码为一个表单,然后传递给url,我能想到的原因就是我的url里面并没有表单吧,不过我对Post也不太陌生,这一块就过了吧
4.requests.put()
如果要更改一组数据中的某一个,必须把其他的也全部一起提交到url
5.requests.patch()
可以向Url提交局部更新请求
此外,网络爬虫有风险,异常处理很重要,所以去爬取网页一定要try-exception!!
robots协议
这就是京东的robots协议
- User-agent: * 是针对所有爬虫
- Disallow:符合后面跟的通配符的内容,就是不允许爬取的信息
- / 表示根目录
不是所有的网站都有robots协议,如果没有,意思就是允许爬虫随便爬取里面的内容
robots协议是建议、约束,网络爬虫也可以不遵守,但是可能存在法律风险
Beautiful Soup
Beautiful Soup库又叫 beautifulsoup4、bs4
import requests
from bs4 import BeautifulSoup
r = requests.get("https://python123.io/ws/demo.html")
demo = r.text
print("解析前:")
print(demo)
soup = BeautifulSoup(demo , "html.parser") # html.parser是解析demo的解析器
print("解析后:")
print(soup.prettify())
感觉beautifulsoup就是给requests返回的网页代码(标签们)整容的,让它的格式变得漂亮(怎么做到的呢??因为调用了prettify方法)~
HTML源码就是标签树,bs4可以把它解析成BeautifulSoup类的一个对象,从而把html内容储存为一个变量,后期就可以对它进行各种操作
BeautifulSoup的各种解析器:
BeautifulSoup类的基本元素:
import requests
from bs4 import BeautifulSoup
r = requests.get("https://python123.io/ws/demo.html")
demo = r.text
soup = BeautifulSoup(demo , "html.parser") # html.parser是解析demo的解析器
print("获取网页的title标签:")
print(soup.title)
print("获取网页的a标签:")
print(soup.a)
如果页面有很多个a标签,上述方法只能获取第一个a标签
对于一个标签,可以通过.name获取标签的名字
import requests
from bs4 import BeautifulSoup
r = requests.get("https://python123.io/ws/demo.html")
demo = r.text
soup = BeautifulSoup(demo , "html.parser") # html.parser是解析demo的解析器
print("a标签的名字")
print(soup.a.name)
print("a标签的父标签的名字")
print(soup.a.parent.name)
print("a标签的父标签的父标签的名字")
print(soup.a.parent.parent.name)
对于一个标签,可以通过.name获取标签的名字.attrs获取它的属性,返回的是字典格式
import requests
from bs4 import BeautifulSoup
r = requests.get("https://python123.io/ws/demo.html")
demo = r.text
soup = BeautifulSoup(demo , "html.parser") # html.parser是解析demo的解析器
print("a标签的属性")
print(soup.a.attrs) # 字典
print()
print("获取a标签class属性的值")
print(soup.a.attrs['class']) # 列表
print()
print("获取标签之间的内容")
print(soup.a.string)
print()
print("查看获取标签之间内容的类型")
print(type(soup.a.string)) # 可以跨标签
此外,关于html的注释问题!
import requests
from bs4 import BeautifulSoup
# r = requests.get("https://python123.io/ws/demo.html")
demo = "<b><!--我是注释--></b> <p>我不是注释</p>"
soup = BeautifulSoup(demo , "html.parser") # html.parser是解析demo的解析器
print("b标签的内容")
print(soup.b.string)
print()
print("p标签的内容")
print(soup.p.string)
print()
print("b标签内容的类型")
print(type(soup.b.string))
print()
print("p标签内容的类型")
print(type(soup.p.string))
从结果就可以很明显的看到,b标签里面明明是注释,但是通过.string方法仍然把它读取出来了,但是注释我们是不需要显示在网页上的,所以在分析文档的时候,要先判断内容的类型,再进一步操作哦~
美丽汤的三种遍历方式:
注意!!!!!!!标签之间的NavigableString也构成了标签树的节点哦
下行遍历:
import requests
from bs4 import BeautifulSoup
r = requests.get("https://python123.io/ws/demo.html")
#demo = "<b><!--我是注释--></b> <p>我不是注释</p>"
demo = r.text
soup = BeautifulSoup(demo , "html.parser") # html.parser是解析demo的解析器
# 下行遍历
print("body标签的儿子节点")
print(soup.body.contents )
print()
print("获取body标签的儿子节点的个数")
print(len(soup.body.contents))
print()
print("获取body标签的第二个儿子节点")
print(soup.body.contents[2])
print()
print("遍历body标签的儿子节点")
for child in soup.body.children:
print(child)
print()
print("遍历body标签的子孙节点")
for child in soup.body.descendants:
print(child)
上行遍历:
import requests
from bs4 import BeautifulSoup
r = requests.get("https://python123.io/ws/demo.html")
#demo = "<b><!--我是注释--></b> <p>我不是注释</p>"
demo = r.text
soup = BeautifulSoup(demo , "html.parser") # html.parser是解析demo的解析器
# 上行遍历
print("html标签的父节点")
print(soup.html.parent )
print()
print("soup标签的父节点")
print(soup.parent)
print()
print("遍历a标签的全部先辈标签")
for parent in soup.a.parents:
if parent is None: # soup没有先辈
print(parent)
else:
print(parent.name)
平行遍历(必须基于同一个父节点):
import requests
from bs4 import BeautifulSoup
r = requests.get("https://python123.io/ws/demo.html")
#demo = "<b><!--我是注释--></b> <p>我不是注释</p>"
demo = r.text
soup = BeautifulSoup(demo , "html.parser") # html.parser是解析demo的解析器
# 平行遍历
print("遍历a标签的后续兄弟标签")
for sibling in soup.a.next_siblings:
print(sibling)
print("遍历a标签的前续兄弟标签")
for sibling in soup.a.previous_siblings:
print(sibling)
总结:
第一天就到这里啦~ 累skr人嘤嘤嘤
信息组织与提取
信息标记的三种形式:
- XML (eXtensible Markup Language) 和html非常非常相似!
- JSON (JavaScript Object Notion) 主要由有类型的键值对组成
- YAML 无类型的键值对组成
所属关系用缩进表示
并列关系用-
| 表达整块数据
三种标记方式的实例对比:
XML
JSON
YAML
三种标记方式的特点:
补充:JSON不能加入注释~
信息提取的方法:
基于bs4库查找HTML的特定内容:
主要就是一个非常腻害的方法——find_all( )
--name--
import requests
import re # 正则表达式库
from bs4 import BeautifulSoup
r = requests.get("https://python123.io/ws/demo.html")
demo = r.text
soup = BeautifulSoup(demo , "html.parser") # html.parser是解析demo的解析器
print("网页中出现的所有a标签")
print(soup.find_all('a'))
print("网页中出现的所有a标签和b标签")
print(soup.find_all(['a','b']))
print("查询网页中用到的全部标签")
for tag in soup.find_all(True):
print(tag.name)
print("查询网页中全部以b开头的标签")
for tag in soup.find_all(re.compile('b')):
print(tag.name)
--attrs--
import requests
import re # 正则表达式库
from bs4 import BeautifulSoup
r = requests.get("https://python123.io/ws/demo.html")
demo = r.text
soup = BeautifulSoup(demo , "html.parser") # html.parser是解析demo的解析器
print("网页中p标签的包含course字符串的信息")
print(soup.find_all('p','course'))
print("网页中id=link1的内容")
print(soup.find_all(id='link1'))
print("网页中id属性值包含link的内容")
print(soup.find_all(id=re.compile('link')))
--recursive--
import requests
import re # 正则表达式库
from bs4 import BeautifulSoup
r = requests.get("https://python123.io/ws/demo.html")
demo = r.text
soup = BeautifulSoup(demo , "html.parser") # html.parser是解析demo的解析器
print("搜索a标签儿子这一层信息")
print(soup.find_all('a',recursive=False))
print("搜索a标签全部子孙们的信息")
print(soup.find_all('a',recursive=True))
--string--
import requests
import re # 正则表达式库
from bs4 import BeautifulSoup
r = requests.get("https://python123.io/ws/demo.html")
demo = r.text
soup = BeautifulSoup(demo , "html.parser") # html.parser是解析demo的解析器
print(soup)
print("检索soup中包含Basic Python的东东")
print(soup.find_all(string = "Basic Python"))
print("检索soup中包含Python的东东")
print(soup.find_all(string = "Python"))
print("检索soup中包含python的东东,正则表达式模糊查找")
print(soup.find_all(string = re.compile("python")))
find_all方法的扩展方法(参数同find_all,同上)
这些方法是用于搜索特定内容,区别在于搜素区域和返回内容范围不同
最后,做一个小实验,提取HTML中所有的url链接
第一步:搜索到所有的<a>标签
第二步:解析<a>标签格式,提取href后的链接内容
import requests
from bs4 import BeautifulSoup
r = requests.get("https://python123.io/ws/demo.html")
#demo = "<b><!--我是注释--></b> <p>我不是注释</p>"
demo = r.text
soup = BeautifulSoup(demo , "html.parser") # html.parser是解析demo的解析器
for link in soup.find_all('a'):
print(link.get('href'))
然后,再做一个关于中国大学排名的实例,具体内容在这里!
正则表达式
regular expression (RE)
正则表达式的基本概念
- 通用的字符串表达框架
- 简洁表达一组字符串的表达式
- 针对字符串表达“简洁”和“特征”思想的工具
- 可以判断某字符串的特征归属
正则表达式的作用
- 表达文本类型的特征
- 同时查找Or替换一组字符串
- 匹配字符串的全部or部分
正则表达式的语法
正则表达式=字符+操作符
例子:
实例,用正则表达式表示IP地址:
分析一波~
\d+. \d+. \d+. \d+ == \d就是单个字符,取值0-9,中间的. 就是Ip地址的分隔符
\d{1,3}就是把前面的字符(\d扩展1-3次),前面的字符范围是0-9,所以后面扩展的时候,范围也是0-9
0-99为什么是 [1-9]?\d 呢,?是对前面的字符扩展0或1次,当扩展0次的时候,最后结果就取决于\d,也就是[0-9],当扩展1次的时候,就可以表示10~99了,真的是非常的精妙了,[1-9]和\d的位置是不能换的,?也必须在中间,一切都是被安排得明明白白的
100-199的表示就稍微简单一点了,百位必须是1所以开始的1放好,后面两位的取值是0-9(\d),必须扩展两次({2}),所以就 1\d{2} 咯
200-249呢,百位是2是定死了的,十位的范围是0-4,必须要有,各位是0-9,也必须要有,所以就 2[0-4]\d
总之,通过这个练习可以发现,要提炼出正则表达式,像数字这些东西,就要一位一位的去看,观察符合要求的数字有哪些特点,再定制出正则表达式。
最后为了表示0-255,就可以把0-99,100-199,200-249,250-255在()包裹起来,再用 | 隔开。
所以最后的结果就是:
满满的细节鸭~~
python中使用正则表达式
import re
在re库中用原生字符串来表示正则表达式,原生字符串就是不包含转移字符的字符串
怎么表示原生字符串呢,其实就是在字符串前面加一个r
例如: r ‘[1-9]\d{5}’
re库里面有很多关于正则表达式的函数,如下:
re.search()
关于参数:re.search(pattern, string, flags=0)
- pattern:正则表达式
- string:带匹配的字符串
- flags:正则表达式控制标记,如下
例子:
import re
string = 'BTS111111 WOS222222 SDW333333 DS444444'
match = re.search(r'[1-9]\d{5}', string)
if match:
print(type(match))
print(match)
print(match.group(0))
我的理解:
对于一个字符串,给一个正则表达式,用Search方法,就可以找出满足正则表达式的字符串,但是它并不是直接返回的字符串,而是返回的一个match对象,要获取到满足正则表达式的字符串,还要.group(0),然后呢,当有多个字符串都满足的时候,只返回第一个的match对象。
re.match()
关于参数:re.match(pattern, string, flags=0)
我的理解:
对于一个字符串,给一个正则表达式,用match方法,就可以找出满足正则表达式的字符串的Match对象,但是哈,一定要从给定字符串的第一个字符串就开始去匹配,如果满足正则表达式的字符串是夹在给定字符串的中间的,这样返回结果是空,就相当与没匹配起。
re.findall()
关于参数:re.findall(pattern, string, flags=0)
我的理解:
对于一个字符串,给一个正则表达式,用findall方法,可以把字符串中满足正则表达式的字符串全部挖出来,然后形成一个列表,相较于search,findall可以找到全部符合的字符串哈~而且直接就是字符串列表了,不是match对象
re.split()
关于参数:re.split(pattern, string, maxsplit, flags=0)
我的理解:
maxsplit可以限制分割的次数,如果超过了分割次数后面的全部都将作为一个整体输出,然后呢,结果也是存在一个列表里面的,其实就是把符合正则表达式的字符串删掉,替换成分隔符,that'all
re.finditer()
关于参数:re.finditer(pattern, string, flags=0)
我的理解:
finditer其实和findall的功能是差不多的,都是针对给定字符串,去找到全部的满足给定正则表达式的字符串,但是返回类型二者有区别,需要区分辨别一下,finditer的返回值是一个迭代类型,每一个迭代元素是一个match对象;findall的返回值就是一个非常简单粗暴的,满足给定正则表达式的字符串列表。
re.sub()
关于参数:re.sub(pattern, repl, string, count=0, flags=0)
import re
string = 'BTS111111 WOS222222 SDW333333 DS444444'
match = re.sub(r'[1-9]\d{5}','哈哈哈哈', string, 2)
if match:
print(match)
我的理解:
sub方法就是在一个字符串中替换匹配正则表达式的子串,然后注意一下返回值是替换后的字符串,多了两个参数,第二个参数repl就是要把匹配正则表达式的子串替换成啥,第四个参数count规定了最大替换次数。
re库的两种等价使用方法
第一种就是我上面练习的时候用的,比较适合只对该正则表达式做一次操作,如果要多次使用,下面那种就更好辣,使用compile方法,可以把正则表达式的字符串形式编译成正则表达式对象,然后存在一个变量里面,然后以后再次使用这个正则表达式的时候,就用编译后的对象辣,要进行上面的操作,就对这个对象使用方法,用法和上面也是一模一样滴~
match对象
其实就是一次匹配的结果,里面包含了相关的信息。
match对象的属性
match对象的方法
直接上例子说明吧~
再举一个栗子!!
import re
string = 'PYANBNCNDN'
pataa = re.compile(r'PY.*N')
match = pataa.search(string)
if match:
print(match.group(0))
其实匹配的结果有四种
- PYAN
- PYANBN
- PYANBNCN
- PYANBNCNDN
但是re库采用的是贪婪匹配,因为search只能输出一个结果,所以就输出最长的那个结果
SO..如果我想得到最短的那个结果该肿么办呢??
那就只有改一下正则表达式了...
最小匹配操作符: