[TOC]

了解爬虫

网络爬虫的分类

  • 通用网络爬虫:抓取系统重要组成部分,抓取的是一整张页面。搜索引擎使用,遵守robopts协议。(robots协议是君子协议,只防君子,不防小人。)
  • 聚焦网络爬虫:又称主题网络爬虫,根据预定义的主题来进行相关页面的爬取(抓取局部),与通用网络爬虫想比,它的爬取范围更为精准(自己写的爬虫程序)
  • 增量式网络爬虫: 对已下载网页采取增量式更新和只爬行新产生的或者已经发生变化网页的爬虫。
  • 深层网络爬虫: Web 页面按存在方式可以分为表层网页和深层网页,深层网页是指大多内容不能通过静态获取,需要用户提交关键词才能获取到隐藏在搜索表单后面的内容。

爬取数据的步骤:

  1. 发送请求:确定需要爬取的URL地址
  2. 获取响应内容:由请求模块向URL地址发出请求,并得到网站的响应
  3. 解析内容:利用解析模块从响应内容中提取所需数据
  4. 保存数据

爬虫入门

URL

1
protocol :// hostname[:port] / path / [;parameters][?query]#fragment

URL由三部分组成,第一部分是协议,有http、https、ftp等,第二部分存放资源的服务器的域名或IP地址,第三部分为资源的具体地址

​ 我们在进行网络请求的时候通常采用三种方式:urllib、urllib3 和 requests,下面我们就来介绍一下 urllib 和 requests。

网络请求

  1. 请求模块是urllib.request,是Python标准库模块。

模块导入

程序的导入方式:

  • import urllib.request
  • from urllib import request

模块内的方法

  • urllib.request.urlopen(URL,timeout)

​ 作用:向网站发起请求并且获取响应对象。

参数 作用
URL 需要爬取的URL地址
timeout 设置等待超时时间,指定时间未响应抛出异常
  • 响应对象(res)的方法

    方法 作用
    res.read() 获取响应内容(字节串)
    res.read().decode() 获取响应内容(字符串)
    res.geturl() 返回实际数据的URL地址
    res.getcode() 获取HTTP响应代码

字节串

什么是字节串
python 中的bytes 类型用来表示一个字节串。他是python3.x新加的类型(对比python2.x)。

​ bytes 只负责以字节序列的形式(二进制形式)来存储数据,至于这些数据到底表示什么内容(字符串、数字、图片、音频等),完全由程序的解析方式决定。如果采用合适的字符编码方式(字符集),字节串可以恢复成字符串;反之亦然,字符串也可以转换成字节串。bytes 类型的数据非常适合在互联网上传输,可以用于网络通信编程;bytes 也可以用来存储图片、音频、视频等二进制格式的文件。

字节串(bytes)和字符串(string)的对比:

​ 字符串由若干个字符组成,以字符为单位进行操作;字节串由若干个字节组成,以字节为单位进行操作。
​ 字节串和字符串除了操作的数据单元不同之外,它们支持的所有方法都基本相同。
​ 字节串和字符串都是不可变序列,不能随意增加和删除数据。

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from urllib import request

# 获取对象
res = request.urlopen(url='https://www.baidu.com')
# 获取网页源代码(默认是字节串,需要转换成字符串)
html = res.read().decode()
# html = response.read().decode('utf-8')#以utf-8格式读取网页的内容
# 获取实际地址
url = res.geturl()
# 返回HTTP响应代码
code = res.getcode()

# 输出信息
print(res)
#print(html)
print(url)
print(code)

结果如下:

image-20221111152246842

  1. requests 模块

         requests是一种**第三方模块**,主要用于发送请求,它在使用的时候比urllib模块要简洁方便很多,我们可以在命令操作符里通过pip install requests来安装,也可以在Pycharm中直接进行安装。
    

    requests 模块也可以使用 get, post, put, delete, hand 等来发送请求。

    我们以 get 方法为例;

    1
    2
    3
    4
    5
    6
    7
    import requests
    r = requests.get('https://www.douban.com/')
    print(r.status_code) # 输出状态码
    print(r.encoding) # 输出编码格式
    print(r.headers) # 输出头部文件
    print(r.cookies) # 输出cookie信息
    print(r.content) # 输出字节流形式网页源码

    注意: 在开启代理(或者说科学上网后),会报错。请把 https 改成 http ;或者关闭掉代理。

伪装成用户

网站是如何判断是正常人类访问还是爬虫访问?

通过请求头(headers)中的User-Agent来判断。

所以我们应该修改User-Agent,具体如下:

使用url.request.Request()方法:

  1. 创建请求对象

  2. 包装请求,重构User-Agent

    常用参数: URL:请求的URL地址

​ headers:添加请求头,类型为字典headers = {‘User-Agent’: ‘ ‘}

字典的值该填什么呢?

打开浏览器 —> F12 (Ctrl+Shift+I) —> network (网络) —> 刷新 —> 点击一个 —> 下划找到 User-Agent 。把值填入字典即可。

image-20221112220239175

代码如下:

1
2
3
4
5
6
7
8
from urllib import request

# 构造请求对象
req = request.Request(url = url,headers=headers)
# 获取响应对象
res = request.urlopen(req)
# 获取响应内容
html = res.read().decode()

实例

1
2
3
4
5
6
7
8
9
10
11
from urllib import request
# 1.定义url和headers
url='http://httpbin.org/get'
headers={'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1'}
# 2.包装请求
req=request.Request(url=url,headers=headers)
# 3.发请求
res=request.urlopen(req)
# 4.获取响应内容
html=res.read().decode()
print(html)

网络超时处理

​ 我们在发送网络请求的时候避免不了超时现象的发生,如果我们不设置一个超时时间,那么系统会因为它长时间未响应而无法打开网页。

​ 超时又分为连接超时读取超时

​ 连接超时就是在程序默认的等待时间内没有得到服务器的响应。

1
2
3
4
5
6
7
8
9
10
11
import datetime
import requests
url = 'http://www.google.com.hk'
print(datetime.datetime.now())
try:
r = requests.get(url, timeout = 5).text # 连接限定时间
print('获得响应')
except requests.exceptions.RequestException as s:
print('连接超时')
print(s)
print(datetime.datetime.now())

结果如下:

1
2
3
4
2022-11-12 22:08:55.395394
连接超时
HTTPConnectionPool(host='www.google.com.hk', port=80): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x0000016A5EEA3520>, 'Connection to www.google.com.hk timed out. (connect timeout=5)'))
2022-11-12 22:09:00.539448

读取限定时间:

1
r = requests.get(url, timeout=(5,10)).text

在这里15即为读取限制时间,如果超过即为读取超时。

URL的一些处理

  1. 给URL地址中查询参数进行编码

​ 导入方式:

1
2
import urllib.parse
from urllib import parse

url不能识别中文,需要编码进行转换

1
2
3
params = {'word':'小璇','pn':'50'}
params = urllib.parse.urlencode(params)
print(params)

结果如下:

image-20221111201007783

  1. 拼接URL地址的三种方式

    • 字符串相加:url = 'http://www.baidu.com/s?' + 编码后的查询参数

    • 字符串格式化(占位符%s):url = 'http://www.baidu.com/s?%s' %编码后的查询参数

    • 字符串的format()方法

      1
      url = 'http://www.baidu.com/s?{}'.format(编码后的参数查询)

练习: 百度搜索”小璇”,并且保存到本地

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from urllib import request
from urllib import parse
# 1.拼接url地址
word = input('请输入百度搜索关键字:')
params=parse.urlencode({'wd':word})
url='http://www.baidu.com/s?{}'.format(params) # 这个url我们需要提前观察它的结构
headers={'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1'}
# 2.发请求获取响应内容
req=request.Request(url=url,headers=headers)
res=request.urlopen(req)
html=res.read().decode()
print(html)
# 3.保存到本地文件
filename=word+'.html'
with ope(filename,'w',encoding='utf-8') as f:
f.write(html)

百度贴吧爬虫案例

步骤

image-20221111210856009

代码如下:

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
from urllib import request
from urllib import parse
import time
class BaiduTiebaSpider:
def __init__(self):
self.ur1 = 'http://tieba.baidu.com/f?kw={}&pn={}'
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1'}

def get_html(self, url):
req = request.Request(url=url, headers=self.headers)
res = request.urlopen(req)
html = res.read().decode()

return html

def parse_html(self):
"""解析提取数据的函数"""

pass

def save_html(self, filename, html):
with open(filename, 'w', encoding='utf8') as f:
f.write(html)

def run(self):
name = input('请输入贴吧名:')
start = int(input('请输入起始页:'))
end = int(input('请输入终止页:'))
params = parse.quote(name)
# 1.拼接url地址
for page in range(start, end + 1):
pn = (page - 1) * 50 # 根据所选贴吧url规律计算得出
url = self.ur1.format(params, pn)
html = self.get_html(url)
filename = '{}_第{}页.html'.format(name, page)
self.save_html(filename, html)
# 终端打印提示
print('第%d页抓取成功' % page)
# 控制数据抓取的频率
time.sleep(random.randint(1, 3))

test = BaiduTiebaSpider()
test.run()

爬取笔趣阁小说案例

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
import requests
from bs4 import BeautifulSoup
import time
import lxml

# 下载链接的前缀,‘944’是《剑来》的地址,想下载其他书可以查看原网页换地址
first_url = 'https://www.bqg99.com/book/944/'

basic_url = {
'whole': None,
'index': 1
}

custom_header = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35'
}

# 下载好的小说
output = 'D:/studyProject/myNote/novel.txt'


# 更新网页链接
def update_link(url_dict):
url_dict['whole'] = first_url + str(url_dict['index']) + '.html'
url_dict['index'] = url_dict['index'] + 1
print(url_dict['whole'])
return url_dict


# 通过GET方法获取网页文本内容
def fetch_text(url_dict, request_header):
data = requests.get(url=url_dict['whole'], headers=request_header, allow_redirects=False)
print('status = %d' % data.status_code)
if data.status_code == 302:
return None
else:
data = BeautifulSoup(data.text, 'lxml')
article = data.find(name='div', class_='content')
chapter_topic = article.h1.text
content_soup = article.find(name='div', id='chaptercontent', class_='Readarea ReadAjax_content')
content_soup.p.decompose() # 去掉多余的“上一章”、“下一章”的导航链接
charter_words = content_soup.stripped_strings
chapter = {
'topic': chapter_topic,
'content': charter_words
}

return chapter


def main():
novel = open(file=output, mode='a+', encoding='utf8')

link = update_link(basic_url)
text = fetch_text(url_dict=link, request_header=custom_header)

while text is not None:
novel.write(text['topic'])
novel.write('\n')
for line in text['content']:
line = line
novel.write(line)
novel.write('\n')
novel.write('\n\n')
time.sleep(1) # 暂停1秒,防止服务器拒绝,不过这个网站好像没有反爬机制

link = update_link(link)
text = fetch_text(url_dict=link, request_header=custom_header)

novel.close()


if __name__ == '__main__':
main()

参考文章:

[1]: https://blog.csdn.net/m0_46213598/article/details/119704825 “python爬虫快速入门”
[2]: https://www.dotcpp.com/course/318 “Python爬虫技术基础”
[3]: https://www.52pojie.cn/thread-1710198-1-1.html “笔趣阁小说爬虫下载”