Python爬虫学习与实践

​ 主要记录一下前段时间在MOOC上学的python网络爬虫课程的学习内容,以及用于大一项目的豆瓣电影评论爬虫实践。

Request库的基本使用

response对象的属性

response对象 属性
r.status_code http请求的返回状态,状态码
r.text url对应的页面内容
r.encoding 从http header中猜测的页面内容的编码方式
r.apparent_encoding 从页面内容分析出的响应内容的编码方式
r.content http响应内容的二进制形式

通用代码框架

1
2
3
4
5
6
7
8
9
Import  requests
def getHTMLText(url): #异常处理
try:
r=requests.get(url, timeout=30)
r.raise_for_status() #返回状态码不是200时返回异常
r.encoding=r.apparent_encoding
return r.text
except:
return “Error”

requests.get()方法

1
r = requests.get(url, **kwargs)

get函数共有十三个控制访问的参数,常用的参数如下:

控制参数 数据类型 功能
params 字典或者字节序列 作为参数增加到url中
headers 字典 http定制头,模拟浏览器访问
cookies 字典 添加访问cookie
timeout - 设定超时时间
proxies 字典 设置代理IP

BeautifulSoup库的基本使用

初始化解析

1
2
3
from bs4 import BeautifulSoup
html = r.text #html 页面内容
soup = BeautifulSoup(html,"html.parser")

BeautifulSoup库的理解

例子:

1
2
3
<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">
Basic Python
</a>

name&parent

1
2
3
4
5
6
>>> soup.a.name
'a'
>>> soup.a.parent.name
'p'
>>> soup.a.parent.parent.name
'body'

attrs

1
2
3
4
5
6
7
8
>>> tag = soup.a
#soup.a读取的是整个a标签及其内部内容
>>> tag.attrs
{'href': 'http://www.icourse163.org/course/BIT-268001', 'class': ['py1'], 'id': 'link1'}
>>> tag.attrs['class']
['py1']
>>> tag.attrs['href']
'http://www.icourse163.org/course/BIT-268001'

string

1
2
3
>>> tag.string
'Basic Python'
#also soup.a.string

标签树的遍历

上行遍历

最常用,后面的遍历其实不太用。即上文提到的parent和parents用法,此处不赘述。

举例:

1
2
3
4
5
6
soup = BeautifulSoup(demo, "html.parser")
for parent in soup.a.parents:
if parent is None:
print(parent)
else:
print(parent.name)

下行遍历

属性 说明
soup.a.contents a标签所有子节点存入的列表
soup.a.children 子节点的迭代类型,用于循环遍历儿子节点
soup.a.descendants 子孙节点的迭代类型,包含所有子孙节点

举例

1
2
3
4
5
6
7
8
9
10
11
>>> soup.head.contents
[<title>This is a python demo page</title>]

>>> soup.body.contents
['\n', <p class="title"><b>The demo python introduces several python courses.</b></p>, '\n', <p class="course">Python is a wonderful general-purpose programming language. You can learn Python from novice to professional by tracking the following courses:
<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a> and <a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>.</p>, '\n']

>>> len(soup.body.contents)
5
>>> soup.body.contents[1]
<p class="title"><b>The demo python introduces several python courses.</b></p>

平行遍历

属性 说明
.next_sibling 返回按照HTLML文本顺序的下一个平行节点标签
.previous_sibling 返回按照HTML文本顺序的上一个平行节点标签
.next_siblings 迭代类型,返回按照HTLML文本顺序的后续所有平行节点标签
.previous_sibling 返回按照HTML文本顺序的前续所有平行节点标签

find函数

主要用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#find_all() 返回列表
>>> soup.find_all('a')
[<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>, <a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>]

#可用for循环迭代操作
>>> for link in soup.find_all('a'):
... print(link.get('href'))
...
http://www.icourse163.org/course/BIT-268001
http://www.icourse163.org/course/BIT-1001870001

#查找多个标签
>>> soup.find_all(['a','b'])
[<b>The demo python introduces several python courses.</b>, <a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>, <a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>]

#查找指定内容
>>> soup.find_all(id='link1')
[<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>]

简化写法:

1
2
<tag>(...)等价于<tag>.find_all(...)
soup(...)等价于soup.find_all(...)

正则表达式及re库

正则表达式的语法

由字符和操作符构成

常用操作符:

操作符 说明 示例
. 表示任何单个字符
[] 字符集,对单个字符给出取值范围 [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
竖杠 左右表达式任意一个
操作符 说明 示例
{m} 扩展前一个字符m次 ab{2}c表示abbc
{m,n} 扩展前一个字符m至n次 ab{1,2}表示abc, abbc
^ 匹配字符串开头 ^abc表示abc且在一个字符串的开头
$ 匹配字符串结尾 abc$表示abc且在一个字符串的结尾
() 分组标记,内部只能使用\ 操作符 (abc)表示abc
\d 数字,等价于[0-9]
\w 单词字符,等价于[A-Za-z0-9]

经典正则表达式实例 :

经典正则表达式实例

正则表达式语法实例

ege2

Re库的基本使用

Re库主要功能函数

函数 说明
re.search() 在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象
re.match() 从一个字符串的开始位置起匹配正则表达式,返回match对象
re.findall() 搜索字符串,以列表类型返回全部能匹配的字串
re.split() 将一个字符串按照正则表达式匹配结果进行分割,返回列表类型
re.finditer() 搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是match对象
re.sub() 在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串

Match对象的属性

属性 说明
.string 待匹配的文本
.re 匹配时使用的pattern对象(正则表达式)
.pos 正则表达式搜索文本的开始位置
.endpos 正则表达式搜索文本的结束位置

Match对象的方法

方法 说明
.group(0) 获得匹配后的字符串
.start() 匹配字符串在原始字符串的开始位置
.end() 匹配字符串在原始字符串的结束位置
.span() 返回(.start(), .end())

最小匹配操作符

操作符 说明
*? 前一个字符0次或无限次扩展,最小匹配
+? 前一个字符1次或无限次扩展,最小匹配
?? 前一个字符0次或1次扩展,最小匹配
{m,n}? 扩展前一个字符m至n次(含n),最小匹配

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#.group(0)用法
>>> match = re.search(r'[1-9]\d{5}','dkfjlsdf610101')
>>> if match :
... print(match.group(0))
610101

#findall用法
>>> ls = re.findall(r'[1-9]\d{5}','dkfjlsdf610101300099')
>>> ls
['610101', '300099']

#split()用法
>>> re.split(r'[1-9]\d{5}','dkfjlsdf610101')
['dkfjlsdf', '']
>>> re.split(r'[1-9]\d{5}','dkfjlsdf610101',maxsplit=1)
['dkfjlsdf', '']
>>> re.split(r'[1-9]\d{5}','dkfjlsdf610101 dfdf300098')
['dkfjlsdf', ' dfdf', '']
>>> re.split(r'[1-9]\d{5}','dkfjlsdf610101 dfdf300098',maxsplit=1)
['dkfjlsdf', ' dfdf300098']

#finditer()用法
>>> for m in re.finditer(r'[1-9]\d{5}','dkfjlsdf610101 dfd300098'):
... if m:
... print(m.group(0))
610101
300098

#sub()用法
>>> re.sub(r'[1-9]\d{5}',':zipcode','dkfjlsdf610101 dfd300098')
'dkfjlsdf:zipcode dfd:zipcode'

#search(), pos(), endpos(), start(), end()用法
>>> m = re.search(r'[1-9]\d{5}','dkfjlsdf610101 dfd300098')
>>> m.string
'dkfjlsdf610101 dfd300098'
>>> m.re
re.compile('[1-9]\\d{5}')
>>> m.pos
0
>>> m.endpos
24
>>> m.start()
8
>>> m.end()
14

Scrapy爬虫框架

emmmm由于实践中还没有应用这个爬虫框架,所以不怎么了解,先挖坑吧,以后来补上。

豆瓣Top250电影评论爬虫

思路

鉴于大一项目需要豆瓣的电影评论数据做情感分析,只好现学现用。豆瓣并没有提供能找到所有电影url链接的页面,所以只好先用top250试试水,后面再想办法爬取更多的电影。

1.前期工作:

  • 人工分析top250和每部电影url的组成规律
  • 人工解析目标在页面的位置、标签名称等

2.主要思路:

  • 从top250页面爬取每部电影的链接
  • 通过该电影链接进入评论页面,获得电影名称,通过改变url参数实现翻页爬取500条评论(需要cookie)

3.难点:

豆瓣的反爬虫机制真的被练出来了。。。每部电影只能显示500条短评,况且还必须登录,否则只能显示200条。说到登录,只能同时登录一个账号(账号还必须用手机号绑定注册),用一个cookie,而且在设置时间延迟的基础上爬十多部电影就会被冻结账号。这个时候,不像网上说的只需要识别验证码了,而是需要手动发短信,修改密码重新登录,再重新复制新的cookie继续爬。另外,从一个免费提供代理ip地址的网站上找了几个代理ip做ip池,每次随机使用一个代理ip。就这样断断续续爬了几天,终于爬好了惹。

代码

emmm没有写注释😂,下次注意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#!/usr/bin/env python3
# -*- coding: utf-8 -*-


from bs4 import BeautifulSoup
import requests
import time
import random

header = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'
}

start_url = "https://movie.douban.com/top250"
cookie = ['cookie.txt', 'cookie2.txt']

f_cookies = open('cookie.txt', 'r')
cookies = {}

proxies = ['183.47.2.201:30278', '118.122.92.252:37901', '163.204.244.141:9999', '58.254.220.116:52470',
'114.119.116.92:61066', '113.108.242.36:47713', '42.123.125.181:8088', '116.7.176.29:8118']

for line in f_cookies.read().split(';'):
name, value = line.strip().split('=', 1)
cookies[name] = value


def get_html_text(url, data):
try:
pro = {'http': random.choice(proxies)}
# cookie = {'cookie': random.choice(cookies)}
r = requests.get(url, params=data, headers=header, cookies=cookies, proxies=pro)
r.raise_for_status()
# r.encoding = r.apparent_encoding
return r.text
except:
print(r.status_code)


def get_each_info(url):
count = 0
filename = ''
url = url + 'comments'
for i in range(0, 500, 20):
time.sleep(6)
print("Page:", 1+i/20)
data = {
"start": i,
"limit": 20,
"sort": "new_score",
"status": "P"
}
html = get_html_text(url, data)
soup = BeautifulSoup(html, "html.parser")
title = soup.find('title')
movie = title.string
if filename.strip() == '':
filename = movie + '.txt'
tag = soup.find_all('span', 'short')

for t in tag:
count = count + 1
with open(filename, 'a') as f:
f.write(str(count))
f.write(":")
if t.string:
f.write(t.string)
else:
f.write("\n")
continue
f.write("\n")


def get_urls():
for i in range(150, 175, 25):
# time.sleep(5)
number = 0
data = {
"start": i,
"filter": ""
}
h = get_html_text(start_url, data)
s = BeautifulSoup(h, "html.parser")
t = s.find_all('span', 'title')
u = ""
for j in t:
if u.strip() == '' or u != j.parent['href']:
number = number + 1
u = j.parent['href']
# if i == 25 and number < 17:
# continue
# if i == 50 and number < 6:
# continue
if i == 150 and number < 23:
continue
else:
time.sleep(5)
print("page:", i, "\t", "Movie No.", number)
get_each_info(u)


get_urls()