当前位置: 代码迷 >> 综合 >> Python爬虫实习笔记 | Week2 Python正则和BeautifulSoup学习与试炼
  详细解决方案

Python爬虫实习笔记 | Week2 Python正则和BeautifulSoup学习与试炼

热度:83   发布时间:2023-11-20 00:38:30.0

2018/10/22 23
1.所思所想:今天状态一直不佳,一是因为自己晚上晚睡,睡眠不足,比较困倦;二是自己爬虫基础还不牢靠,还需要努力学习,比较惭愧;三是之前的项目,组长赵某乃不值得信赖之人物,使得自己多生烦忧,《MySQL》也上交了。。还好下午把学长写的爬虫跑通了,今天下午把代码理解一遍,然后自己跑一个城市。

2.工作:
(1).《Python爬虫项目实战》中的123Chapter,感觉自己对爬虫理解也更加深刻,《数学之美》在“道”上给了我很多启发。对爬虫基础部分我也做了总结,之后写在博客上。
(2).下午我把Python的正则部分看了一下,只能说掌握了核心要点,但已经足够处理项目我想,之后有时间我应该系统学习一下。
(3).Beautifulsoup很强大,但和CSS选择器结合,以及它本身的特性不是很了解,昨天看了一下,不是能透彻理解,明天还要继续看。

Python3
用requests模块抓取网页资源
1.使用GET请求:
import requests

r = request.get('http://www.zhihu.com')
print(r.content)
-------------------------------
在链接后添加后缀的请求
import requests

payload = { 'Keywords' : 'blog:qiyeboy', 'pageindex' : 1 }
r = requests.get('http://www.baidu.com', params = payload)

2.使用POST请求
import requests

postdata = { 'key' : 'data' }
r = request.post('http://www.zhihu.com', data=postdata)

print( r.content )  #以字节的形式返回内容
print( r.text ) #以文本的形式返回内容
print( r.encoding ) #返回网页的编码格式

3.编码设置:
为了防止Requests猜测编码错误,方法1是自行设置编码格式:r.encoding = 'utf-8',方法2是使用非常优秀的字符串/文件编码检测模块chardet,使用chardet.detect()返回字典,其中condidence是检测精度,encoding是编码形式。
import requests
import chardet

r = requests.get('http://www.baidu.com')
r.encoding = chardet.detect(r.content)['encoding']
print( r.text )

4.请求头headers处理
import requests

user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Window NT)'
headers = { 'User-Agent' : user_agent }
r = requests.get('http://www.baidu.com', headers = headers)
print( r.content )

5.响应码code和响应头headers处理
import requests

r = requests.get('http://www.baidu.com')
if r.status_code == requests.codes.ok:
    print( r.status_code )
    print( r.headers )
    print( r.headers.get('content-type') )

6.Cookie处理
import requests

user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = {'User-Agent' : user_agent}
r = requests.get('http://www.baidu.com', headers = headers)
#遍历出所有的cookie值字段的值
for cookie in r.cookies.keys():
    print( cookie + ':' + r.cookies.get(cookie) )

7.重定向
处理重定向只是需要设置一下allow_redirects字段即可,例如 r = requests.get('http://www.baidu.com', allow_redirects=True)。将allow_redirects设置为True,则是允许重定向;设置为False,则禁止重定向。

import requests

r = requests.get('http://www.github.com')
print( r.url )
print( r.status_code )
print( r.history )

8.超时设置
requests.get('http://www.github.com', timeout = 2)

9.代理设置
import requests

proxies = {
    'http' : 'http://0.10.1.10:3128',
    'https' : 'http://10.10.1.10:1080'
}
requests.get('http:example.org', proxies = proxies)

主题2:正则表达式
正则表达式是由普通字符(例如字符a到z)以及特殊字符(称为“元字符”)组成的文字模式。模式用于在搜索文本时要匹配一个或多个字符串。

(1).常见的元字符如下:
.    匹配除换行符以外的任意字符
\b    匹配单词的开始和结束
\d    匹配数字
\w    匹配字母,数字,下划线和汉字
\s    匹配任意空白符,包括空格,制表符(Tab),换行符,中文全角空格等
^    匹配字符串的开始
$    匹配字符串的结束

Examples:
1.匹配所有以s开头的单词    \bs\w*\b
2.匹配以s开头后跟数字的字符串(比如:s100)   ^s\d*$
3.匹配网址(比如:www.google.com)   www\.google\.com

(2).重复:
*    重复零次或更多次
+    重复一次或更多次
?    重复零次或一次
{n}    重复n次
{n,}    重复n次或者更多次
{n,m}    重复n到m次

Examples:
1.匹配hello后面跟1个或更多数字    hello\d+
2.匹配5到12个数字的字符串    ^\d{5,12}$
3.匹配we后面跟0个或者1个数字    we\d?

(3).字符集合:
[...], 利用自定义的字符集合,可以匹配几个字符中的单个字符,以及字符范围内的单个字符。

Examples:
1.匹配a,b,c,d,e中的单个字符    [abcde]
2.匹配0到8里的数字        [0-8]
3.匹配所有的字母和数字        [0-9a-zA-Z]

(4).分支条件
正则表达式里的分支条件指的是有几种匹配规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用“|”把不同的规则分隔开。

Examples:
1.匹配电话号码,一种是3位区号,8位本地号,比如:010-11223344;另一种是4位区号,7位本地号,比如:0321-1234567        0\d{2}-\d{8}|0\d{3}-\d{7}
2.匹配IP地址        ((25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.){3}((25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d))

(5).反义
有时候需要查找除某一类字符集合以外的字符,这时就需要使用反义。
常用的反义:
\W    匹配任意不是字母,数字,下划线,汉字的字符
\S    匹配任意不是空白符的字符
\D    匹配任意非数字的字符
\B    匹配不是单词开头或结束的位置
[^a]    匹配除了a以外的任意字符
[^abcde]匹配除了a,b,c,d,e以外的任意字符
[^(123|abc)]    匹配除了1,2,3 或者a,b,c这几个字符以外的任意字符

(6).后向引用

(7).零宽断言

(8).贪婪与懒惰
当正则表达式中包含能接受重复的限定符时,通常的行为是匹配尽可能多的字符,这就是贪婪模式。
以表达式a\w+b为例,如果搜索a12b34b,最后会匹配a12b34b,而不是a12b。如果想匹配a12b,应使用“?”来开启懒惰模式。
*?    重复任意次,但尽可能少重复
+?    重复1次或更多次,但尽可能少重复
??    重复0次或1次,但仅可能少重复
{n,m}    重复n到m次,但尽可能少重复
{n,}?    重复n次以上,但尽可能少重复
    
(9).处理选项
。。。

Python与正则
对不同的语言,对正则表达式的语法绝大部分都是支持的,但是还是有略微不同,每种语言都有一些独特的匹配规则。
Python的匹配规则:
\A    仅匹配字符串开头
\Z    仅匹配字符串末尾
(?P<name>)    分组,除了原有编号外再指定一个额外的别名
(?P=name)    引用别名为<name>的分组匹配到的字符串


Python 通过re模块提供对正则表达式的支持
使用re的一般步骤是:
(1).先将正则表达式的字符串形式编译成Pattern实例;
(2).然后使用Pattern实例处理文本并获得匹配结果;
(3).最后使用Match实例获得信息。

1.re.compile(string, [flags]) #它将一个正则表达式转化为Pattern匹配对象
Example: pattern = re.compile(r'\d+')

2.re.match(pattern, string, [flags]) <等价于 pattern.match(string, [flags])>#该函数是从输入参数string(匹配的字符串)的开头开始,尝试匹配pattern,一直向后匹配,如果遇到无法匹配的字符或者已经到达string的末尾,立即返回None,反之,获得匹配的结果。
Example:
# 1.将正则表达式编译成pattern对象
pattern = re.compile(r'\d+')
# 2.使用re.match匹配文本,获得匹配结果,无法匹配时将返回None
result1 = re.match(pattern, '192abc')
if result1:
    print(result1.group())
else:
    print('匹配失败1')
result2 = re.match(pattern, 'abc192')
if result2:
    print(result2.group())
else:
    print('匹配失败2')
Result:
192
匹配失败2

3.re.search(pattern, string, [flags]) <等价于 pattern.search(string, [flags])> #search方法和match方法很类似,只是search()会扫描整个string查找匹配,而不是从string的开始位置进行匹配
Example:
# search方法的返回对象在方法和属性上和match是一致的
import re

# 将正则表达式编译成pattern对象
pattern = re.compile(r'\d+')
result1 = re.search(pattern, 'abc192edf333')
if result1:
    print(result1.group())
else:
    print('匹配失败1')
Result:
192

4.re.split(pattern, string, [flags])  # split 能够匹配的子串将string分割后返回列表
Example:
import re

pattern = re.compile(r'\d+')
print(re.split(pattern, 'a1b2c3d4'))
Result:
['a', 'b', 'c', 'd', '']

5.re.findall(pattern, string, [flags])  # findall 搜索整个string,以列表的形式返回能匹配的全部子串
Example:
import re

pattern = re.compile(r'\d+')
print(re.findall(pattern, 'a1b2c3d4'))
Result:
['1', '2', '3', '4']

6.re.finditer(pattern, string, [flags]) # finditer 搜索整个string,以迭代器形式返回能匹配的全部Match对象
Example:
import re

pattern = re.compile(r'\d+')
matchiter = re.finditer(pattern, 'a1b2c3d4')
for match in matchiter:
    print(match.group())
Result:
1
2
3
4
7.re.sub(pattern, repl, string [,count])
Example:
import re

p = re.compile(r'(?P<word1>\w+) (?P<word2>\w+)')  #使用名称引用
s = 'I say, hello, world!'
print(re.sub(p, '111', 'asd f?fdf ss!fdas ss@'))
Result:
111?111!111@

8.re.subn(pattern, repl, string [,count]) #返回( sub(repl, string[, count]), 替换次数 )

主题3:强大的BeautifulSoup
1.简要介绍:BeautifulSoup是一个可以从HTML或XML文件中提取数据的Python库,它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式。

2.Beautiful Soup的安装
方法1: pip install bs4
方法2:在Pycharm中,可以在File -> Settings -> Project Interpreter -> 右侧有个加号按钮 -> 在弹出的窗口搜索bs4并安装。

3.BeautifulSoup的使用
[0].bs4库的导入
from bs4 import BeautifulSoup
[1].创建BeautifulSoup对象
以下'lxml'是手动指定的解析器。如果省略,BeautifulSoup一般会选择最合适的解析器来解析这段文档,如果手动指定,那么BeautifulSoup会选择指定的解析器来解析文档。
方式1. 直接通过字符串创建
soup = BeautifulSoup(html_str, 'lxml', from_encoding = 'utf-8')

    Example:
    from bs4 import BeautifulSoup
    import requests
    import chardet

    url = 'http://www.baidu.com'
    response = requests.get(url)
    response.encoding = chardet.detect(response.content)['encoding']
    text = response.text

    soup = BeautifulSoup(text, 'lxml')
    print(soup.prettify())
方式2. 通过html文件来创建
    from bs4 import BeautifulSoup
    import requests
    import chardet


    url = 'http://www.baidu.com'
    response = requests.get(url)
    response.encoding = chardet.detect(response.content)['encoding']
    text = response.text

    with open('hell', 'w') as fout:
        fout.write(text)

    soup = BeautifulSoup(open('hell'), 'lxml')
    print(soup.prettify())
[2].对象种类
BeautifulSoup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:
a. Tag
b. NavigableString
c. BeautifulSoup
d. Comment

1). Tag
Tag对象与XML或HTML原生文档中的Tag相同,通俗点说就是标记。标记及其里面的内容称为Tag对象。
import requests
import chardet

url = 'http://www.baidu.com'
response = requests.get(url)
response.encoding = chardet.detect(response.content)['encoding']
text = response.text

with open('hell', 'w') as fout:
    fout.write(text)

soup = BeautifulSoup(open('hell'), 'lxml')
# print(soup.prettify())
print(soup.title)
print(soup.a)

Result:
<title>百度一下,你就知道</title>
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>

从栗子中可以看出,利用soup加标记名就可以很容易这些标记的内容,比之前讲的正则表达式简单多了。不过利用这种方式,查找的时所有内容中第一个符合要求的标记。

Tag中有两个最重要的属性:name和atrributes。
.name
每个Tag都有自己的名字,通过.name来获取
print( soup.name )
print( soup.title.name )

Result:
[document]
title
soup对象本身比较特殊,它的name为[document],对于其他内部标记,输出的值便是标记本身的名称。
Tag不仅可以获取name,还可以修改name,改变之后将影响所有通过当前BeautifulSoup对象生成的HTML文档。
soup.title.name = 'mytitle'
print( soup.title )
print( soup.mytitle )

Result:
None
<mytitle>百度一下,你就知道</mytitle>

属性:
Tag的属性的操作方法与字典相同,直接获取; 也可以直接‘点’取属性,.attrs,用于获取Tag中的所有属性。
print( soup.a['href'] )
print( soup.a.attrs['href'] )


2).NavigableString
我们已经得到了标记的内容,要想获取标记内部的文字怎么办呢?需要用到.string
Example:
print( soup.title.string )
print( type(soup.title.string) )

Result:
百度一下,你就知道
<class 'bs4.element.NavigableString'>

注:BeautifulSoup用NavigableString类来包装Tag中的字符串,一个Navigable字符串与Python中的Unicode字符串相同。

3).BeautifulSoup对象表示的是一个文档的全部内容。大部分时候,可以把它当作Tag对象,是一个特殊的Tag对象,因为BeautifulSoup对象并不是真正的HTML或XML的标记,所以它没有name和attribute属性。但为了将BeautifulSoup对象标准化为Tag对象,实现接口的统一,我们依然可以分别获取它的name和attribute属性。

4).Comment
Tag, NavigableString, BeautifulSoup几乎覆盖了HTML和XML中的所有内容,但是还是有些特殊对象。容易让人担心的内容是文档的注释部分:如果不清楚这个标记.string的情况下,可能会造成数据提取混乱。

[3].遍历文档树
×××重中之重
[4].搜索文档树
BeautifulSoup定义了很多搜索方法,这里着重介绍find_all()方法,其他方法的参数和用法类似,请大家举一反三。
find_all方法,用于搜索当前Tag的所有Tag子节点,并判断是否符合过滤器的条件,函数原型如下:
find_all(name, attrs, recursive, text, **kwargs)
1).name参数
name参数可以查找所有名字为name的标记,字符串对象会被自动忽略掉。name参数值可以是字符串,正则表达式,列表,True和方法。
字符串:print( soup.find_all('a') )
正则表达式:print( soup.find_all(re.compile('^b')) )
列表:print( soup.find_all(['a', 'b']) )
True: print( soup.find_all(True) )
2).kwargs参数
kwargs参数在Python里表示为keyword参数。如果一个指定名字的参数不是搜索内置的参数名

[5].CSS选择器
我们也可以利用CSS选择器来筛选元素,在写CSS时,标记名前不加任何修饰,类名前加‘.’, id前加‘#’。用到的方法是soup.select(),返回类型时list。
1).通过标记名称进行查找
# 直接查找title标记
print( soup.select('title') )

# 逐层查找title标记
print( soup.select('html head title') )

# 查找直接子节点
# 查找head下的title标记
print( soup.select('head > title') )
# 查找p下的id="link1"的标记
print( soup.select('p > #link1') )

#查找兄弟节点
#查找id='link1'之后class=sister的所有兄弟标记
print( soup.select('#link1 ~ .sister') )
#查找紧跟id = 'link1' 之后class=sister的子标记
print( soup.select('#link1 + .sister') )

2).通过CSS的类名查找
print( soup.select('.sister') )
print( soup.select('[class~=sister]') )

3).通过tag的id查找
print( soup.select('#link1') )
print( soup.select('a#link2') )

4).通过是否存在某个属性来查找
print( soup.select('a[href]') )

5).通过属性值来查找
Tag[attribute]        用于选取带有指定属性的元素
Tag[attribute=value]    用于选取带有指定属性和值的元素
Tag[attribute~=value]    用于选取属性值中包含指定词汇的元素
Tag[attribute|=value]    用于选取带有以指定值开头的属性值的元素,该值必须是整个单词
Tag[attribute^=value]    匹配属性值以指定值开头的每个元素
Tag[attribute$=value]    匹配属性值以指定值结尾的每个元素
Tag[attribute*=value]    匹配属性值中包含指定值的每个元素    


2018/10/24
1.所思所想:今天上午将BeautigulSoup知识点整理了下,感觉对这一块也更加理解。对于规范化的数据文本,比如html文本,应该使用BeautifulSoup来处理;而非规范化的数据,则只能用Python中的正则了。中午公司福利,吃得很嗨,感觉自己做人做事方面仍然有很多改进空间。下午,又开始跑哈尔滨市,开始明白get和post的不同。自己需要增长的方面还有非常多。

2.工作:
(1).整理BeautifulSoup知识点,对BeautifulSoup更加理解,灵活运用;
(2).深刻理解get和post的不同。如何区别get和post请求? 按F12键->网络,然后在分页面切换页面,会看到类型为html的文件的请求的类型; 也可以直接查看消息头中的消息头。第一,get请求在分页中的网址往往是变化的,而post请求在分页中的地址往往是不变的,但并不绝对,比如哈尔滨环保局的行政处罚页面。第二,post请求在爬取数据时,还要往requests.post()中添加参数data = postdata,postdata数据可能是变化的,也可能是不变的,这个数据可以在 F12 -> 参数中查看。第三,get请求的爬取网页可以直接在网址中查看,而post请求的爬取网页必须在消息头中查看。

2018/10/25
1.所思所想:昨晚就听马亮学长说第二天有显示器啥的搬过来,还是挺激动的,因为我这儿笔记本显示屏比较小,上午帮忙搬显示屏,然后mysql遇到问题,很头疼,网上也没找到好的办法,只好卸载重装。上午,然后是我们Python爬虫后端的开会,这是我第一次参与开会,主要就是讲了目前仍存在的问题以及后期的改进,我意识到,自己仍存在很多问题需要改进。下午跑了防城港的环评,成功惹!!!第一次跑通,哭泣。

2.工作:
(1).今天把整个项目代码不熟悉的部分又看了一下,感觉自己的python还需要提升。对爬虫的大部分概念我应该已经掌握,但技术细节仍然有很多需要弥补的地方。现在主要还是将自己需要做好的做好。
(2).开会。思考如何设计更好的脚本框架。

2018/10/26
1.所思所想:昨晚睡了晚了些,今早困了些。然后,最大的感受是,要多和别人讨论,多吸纳别人意见,有问题多交流。

2.工作:
(1).今天上午学习了beautifulSoup新知识,对于网页处理有了更多的工具;另外,对于自动更新思考了下,写了简单的脚本,觉得还行吧.
(2).下午,主要就是处理时间更新所遇到的问题,对问题解决的越深入,越能感觉到自己仍有较大提升空间;