python爬虫学习笔记3封装爬虫类

在完成了基本的爬取任务之后,接到了将其封装为一个爬虫类的任务

传送门:

前言

转载注明出处。

任务介绍

1、尝试不使用 session 去进行爬取,最好能将 cookies 保存下来可以供下次使用。
2、第二个是尝试将这些封装成面向对象的方式,模拟登陆,爬取,解析,写入数据库这几个部分分离开来。

先做第二个任务

过程记录

190310 周日

创建爬虫类

1
2
3
4
5
6
7
8
class spider:
'''
爬虫类
'''

def __init__(self):
self.session=requests.session()#初始化登录session
self.is_login=False#登录状态

获取登录所需信息

获取登录信息(账号密码以及校验码)这部分与登录可以分开,单独写一个成员函数。

在输入密码这个地方,本来查到可以使用getpass这个库里面的getpass()函数来使用类似 linux 的密码不回显,用法如下:

1
2
3
import getpass
passwd=getpass.getpass()
print(passwd)#测试用输出

但是直接在 pycharm 里面运行是会卡在输入那里,并且也会回显。后来查到了,这个方法是在命令行当中才管用,我试了一下在 python 命令行中使用,

1
2
3
4
5
6
>>>import getpass
>>>passwd=getpass.getpass()
Warning: Password input may be echoed.
Password: >? 123
>>>print(passwd)
123

虽然可以使用了,但是仍然会回显。所以这个命令行说的应该不是 python 命令行,而是 cmd 或者 shell。

在虚拟环境的 cmd 里面,成功了,Password 后面未回显我的输入,下面的数字是测试用的输出,将密码打印出来。

1
2
3
(venv) F:\DEVELOP\py_develop\spider>python test.py
Password:
123

不过为了方便调试代码,我还是使用了input()函数

参考链接:

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
def get_login_data(self,login_url):
'''
获取登录需要的数据
:param login_url: 登录页面url
:return: 一个存有登录数据的字典
'''
# 获取登录校验码
html = self.session.post(login_url, headers=self.headers).text
soup = BeautifulSoup(html, 'lxml')
lt = soup.find('input', {'name': 'lt'})['value']
dllt = soup.find('input', {'name': 'dllt'})['value']
execution = soup.find('input', {'name': 'execution'})['value']
_eventId = soup.find('input', {'name': '_eventId'})['value']
rmShown = soup.find('input', {'name': 'rmShown'})['value']
login_data = {
'username': input("请输入学号:"),
'password': input("请输入密码:"),
'btn': '',
'lt': lt,
'dllt': dllt,
'execution': execution,
'_eventId': _eventId,
'rmShown': rmShown
}
return login_data

登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def login(self,login_url):
"""
登录并返回已经登录的会话
:return: 已经登录的会话(session)
"""
login_data=self.get_login_data(login_url)#获取登录信息

# 登录
response = self.session.post(login_url, headers=self.headers, data=login_data)
if response.url!=login_url:#如果没有跳转回登录页面,那么就是登录成功
print("登录成功")
self.is_login=True
else:
print("登录失败")
return self.session

day8 进度

  1. 了解了一下 Python 类与对象的语法,尝试将代码封装到类中(一些中间代码未保留),不过想要将它改的有通用性(能够爬取其他网站)有些困难,还是先固定只能爬取信息门户
  2. 接下来的计划:将类完成之后再慢慢优化,学习使用 cookie 代替 session 保持登录,以及数据库的更多知识

190311 周一

day9 进度

  1. 图书馆借了一本 mysql 的书籍,在 mysql 命令行上练习创建数据库,表以及字段的操作
  2. 在将代码封装成类的过程中,学习了如何将参数作为一个字典传入,以及将一个字典作为参数传入

190312 周二

获取单页目录内的公告 url

目录网页的内容:

  • 关于……的通知
  • 关于……获奖
  • ……
  • 2700 条记录,分为 138 页显示,下一页
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
def get_url_from_cata(self,url,params):
'''
返回当前页面的url组成的列表
:param url: 无参数的url#如:http://portal.xxx.edu.cn/detach.portal
:param params:url的?后参数#如:?pageIndex=1
:return:以页面指向的标题和url组成的元组为元素的列表,即[(title,content),(title,content)]的形式
'''

#获取url域名部分
#如:http://portal.xxx.edu.cn
base=url.split('/')
base=base[0]+'//'+base[2]

#获取当前页所有链接
html = self.session.post(url,params=params).text#用params参数来拼接参数
soup = BeautifulSoup(html, 'lxml')
rss_title = soup.find_all('a', class_='rss-title')#获取所有链接

result_list=[]
for url in rss_title:
title=url.get_text().strip()
page_url=base+'/'+url['href']#将url拼接完整
l=(title,page_url)
result_list.append(l)

#print(result_list)
return result_list

获取所有目录内的公告 url

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
def get_url_from_cata_all(self, url):
'''
获取页面的底部跳转到其他页的链接并获取目录,给出一个目录页的url,获取相关的所有目录页的url并获取链接
:param url: 其中任何一个目录页的url#如:http://portal.xxx.edu.cn/detach.portal?pageIndex=1
:return:以所有页面的标题和url组成的元组为元素的列表,即[(title,content),(title,content)]的形式
'''

#获取除去参数之后的url
#如:http://portal.xxx.edu.cn/detach.portal
base=url.split('?')[0]

html = self.session.post(url).text
soup = BeautifulSoup(html, 'lxml')
# 获取页数
reg = '共.*?条记录 分(.*?)页显示'
reg = re.compile(reg, re.S)
num = int(re.findall(reg, html)[0])

#获取url
para = {
'pageIndex': 1,
'pageSize': '',
'.pmn': 'view',
'.ia': 'false',
'action': 'bulletinsMoreView',
'search': 'true',
'groupid': 'all',
'.pen': 'pe65'
}
ret=[]
for i in range(1,num+1):
ret.extend(self.get_url_from_cata(base,params=para))
para['pageIndex'] = i

return ret

day10 进度

实现了自动获取目录页数,并从每一页目录获取所有的 url,返回当前所有公告的 url 的列表

190313 周三

获取正文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def get_page(self,url):
'''
提取页面中的公告正文
:param url: 页面url
:return: 正文
'''
html = self.session.post(url, headers=self.headers).text
soup = BeautifulSoup(html, 'lxml')


bulletin_content = soup.find('div', class_='bulletin-content')

bulletin_content =bulletin_content.get_text()

return bulletin_content

保存到 txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def save_by_txt(self,file_content,file_name):
'''
获取单个公告页面的公告并保存到txt
:param file_content:文件内容(str)
:param file_name:输出文件名(str)
:return:无
'''
# 转换为可以作为文件名字的形式
reg = r'[\/:*?"<>|]'
file_name = re.sub(reg, "", file_name)

with open(file_name, 'w', encoding='utf8') as fout:
fout.write(file_content)

print('成功保存到{}'.format(file_name))

保存到 db

1
2
3
4
5
6
7
def save_by_db(self,content,title):
#未改造完成
db = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root', db='news', charset='utf8')
cursor = db.cursor()
cursor.execute("insert into spider(`title`,`content`) values('{0}','{1}')".format(title, content))
db.commit()
print('已经成功保存公告到数据库:“{}”'.format(title))

day11 进度

尝试将保存到数据库的函数里面的数据库参数放到函数形参处,怎么弄都觉得不太合适,于是还是将原本的代码放入

190314 周四

参考链接:

首先是库

1
import http.cookiejar

初始化

1
2
3
4
5
def __init__(self,headers):
self.session=requests.session()#初始化登录session
self.is_login=False#登录状态
self.headers=headers#头信息
self.cookiejar=http.cookiejar.LWPCookieJar('cookie.txt')

大概是将已登录的 session 对象的 cookies 转换为字典(用了一个类似列表生成式的东西,查了一下,是字典生成式,python 还真是方便,这么多简写方式),然后保存到 cookiejar 对象中,调用save()函数来将 cookie 内容保存到第一个参数指定的文件中,即使 cookie 已经被抛弃和过期。

1
2
3
4
def save_cookie(self):
requests.utils.cookiejar_from_dict({c.name: c.value for c in self.session.cookies}, self.cookiejar)
# 保存到本地文件
self.cookiejar.save('cookies', ignore_discard=True, ignore_expires=True)

首先初始化一个 LWPCookieJar 对象

1
load_cookiejar = http.cookiejar.LWPCookieJar()

接着从文件中加载 cookie

1
load_cookiejar.load('cookies', ignore_discard=True, ignore_expires=True)

这里有个问题,这里如果加载失败了(没有这个文件,之前没有保存),需要知道已经失败了。所以使用一个 try 语句块测试一下。

然后把这个 LWPCookieJar 对象给转换成字典,再转换赋值给 session.cookie,这样就加载成功了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def load_cookie(self):
'''
加载cookie
:return: 是否成功
'''
load_cookiejar = http.cookiejar.LWPCookieJar()
# 从文件中加载cookies(LWP格式)
try:
load_cookiejar.load('cookies', ignore_discard=True, ignore_expires=True)
except:
return False

# 转换成字典
load_cookies = requests.utils.dict_from_cookiejar(load_cookiejar)
# 将字典转换成RequestsCookieJar,赋值给session的cookies.
self.session.cookies = requests.utils.cookiejar_from_dict(load_cookies)
return True

修改后的 login()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def login(self,login_url):
"""
登录并返回已经登录的会话
:return: 已经登录的会话(session)
"""
if self.load_cookie():
self.is_login = True
else:
#获取登录信息
login_data=self.get_login_data(login_url)

# 登录
response = self.session.post(login_url, headers=self.headers, data=login_data)
if response.url!=login_url:
print("登录成功")
self.is_login=True
self.save_cookie()
else:
print("登录失败")
return self.session

day12 进度

  1. 完成了爬虫类的封装
  2. 使用 http.cookiejar 库实现了登录一次,在 cookie 有效期内不必再次登录的功能

代码总览

import

1
2
3
4
5
import requests
from bs4 import BeautifulSoup
import pymysql
import re
import http.cookiejar

构造函数

1
2
3
4
5
6
7
8
9
10
class spider:
'''
爬虫类
'''

def __init__(self,headers):
self.session=requests.session()#初始化登录session
self.is_login=False#登录状态
self.headers=headers#头信息
self.cookiejar=http.cookiejar.LWPCookieJar('cookie.txt')

获取登录信息

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
def get_login_data(self,login_url):
'''
获取登录需要的数据
:param login_url: 登录页面url
:return: 一个存有登录数据的字典
'''
# 获取登录校验码
html = self.session.post(login_url, headers=self.headers).text
soup = BeautifulSoup(html, 'lxml')
lt = soup.find('input', {'name': 'lt'})['value']
dllt = soup.find('input', {'name': 'dllt'})['value']
execution = soup.find('input', {'name': 'execution'})['value']
_eventId = soup.find('input', {'name': '_eventId'})['value']
rmShown = soup.find('input', {'name': 'rmShown'})['value']
login_data = {
'username': input("请输入学号:"),
'password': input("请输入密码:"),
'btn': '',
'lt': lt,
'dllt': dllt,
'execution': execution,
'_eventId': _eventId,
'rmShown': rmShown
}
return login_data

登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def login(self,login_url):
"""
登录并返回已经登录的会话
:return: 已经登录的会话(session)
"""
if self.load_cookie():
self.is_login = True

else:

#获取登录信息
login_data=self.get_login_data(login_url)

# 登录
response = self.session.post(login_url, headers=self.headers, data=login_data)
if response.url!=login_url:
print("登录成功")
self.is_login=True
self.save_cookie()
else:
print("登录失败")
return self.session

获取单页目录

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
def get_url_from_cata(self,url,params):
'''
返回当前页面的url组成的列表
:param url: 无参数的url
:param params:url的?后参数
:return:以页面指向的标题和url组成的元组为元素的列表,即[(title,content),(title,content)]的形式
'''

#获取url域名部分
base=url.split('/')
base=base[0]+'//'+base[2]

#获取当前页所有链接
html = self.session.post(url,params=params).text#用params参数来拼接参数
soup = BeautifulSoup(html, 'lxml')
rss_title = soup.find_all('a', class_='rss-title')#获取所有链接

result_list=[]
for url in rss_title:
title=url.get_text().strip()
page_url=base+'/'+url['href']#将url拼接完整
l=(title,page_url)
result_list.append(l)

#print(result_list)
return result_list

获取全部目录

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
def get_url_from_cata_all(self, url):
'''
获取页面的底部跳转到其他页的链接并获取目录,给出一个目录页的url,获取相关的所有目录页的url并获取链接
:param url: 其中任何一个目录页的url
:return:以所有页面的标题和url组成的元组为元素的列表,即[(title,content),(title,content)]的形式
'''

#获取除去参数之后的url
base=url.split('?')[0]

html = self.session.post(url).text
soup = BeautifulSoup(html, 'lxml')
# 获取页数
reg = '共.*?条记录 分(.*?)页显示'
num = int(re.findall(reg, html)[0])

#获取url
para = {
'pageIndex': 1,
'pageSize': '',
'.pmn': 'view',
'.ia': 'false',
'action': 'bulletinsMoreView',
'search': 'true',
'groupid': 'all',
'.pen': 'pe65'
}
ret=[]
for i in range(1,num+1):
ret.extend(self.get_url_from_cata(base,params=para))
para['pageIndex'] = i

return ret

获取正文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def get_page(self,url):
'''
提取页面中的公告正文
:param url: 页面url
:return: 正文
'''
html = self.session.post(url, headers=self.headers).text
soup = BeautifulSoup(html, 'lxml')


bulletin_content = soup.find('div', class_='bulletin-content')

bulletin_content =bulletin_content.get_text()

return bulletin_content

保存到 txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def save_by_txt(self,file_content,file_name):
'''
获取单个公告页面的公告并保存到txt
:param file_content:文件内容(str)
:param file_name:输出文件名(str)
:return:无
'''
# 转换为可以作为文件名字的形式
reg = r'[\/:*?"<>|]'
file_name = re.sub(reg, "", file_name)

with open(file_name, 'w', encoding='utf8') as fout:
fout.write(file_content)

print('成功保存到{}'.format(file_name))

保存到数据库

1
2
3
4
5
6
def save_by_db(self,content,title):
db = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root', db='news', charset='utf8')
cursor = db.cursor()
cursor.execute("insert into spider(`title`,`content`) values('{0}','{1}')".format(title, content))
db.commit()
print('已经成功保存公告到数据库:“{}”'.format(title))
1
2
3
4
def save_cookie(self):
requests.utils.cookiejar_from_dict({c.name: c.value for c in self.session.cookies}, self.cookiejar)
# 保存到本地文件
self.cookiejar.save('cookies', ignore_discard=True, ignore_expires=True)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def load_cookie(self):
'''
加载cookie
:return: 是否成功
'''
load_cookiejar = http.cookiejar.LWPCookieJar()
# 从文件中加载cookies(LWP格式)
try:
load_cookiejar.load('cookies', ignore_discard=True, ignore_expires=True)
except:
print('cookie加载失败')
return False

# 转换成字典
load_cookies = requests.utils.dict_from_cookiejar(load_cookiejar)
# 将字典转换成RequestsCookieJar,赋值给session的cookies.
self.session.cookies = requests.utils.cookiejar_from_dict(load_cookies)
return True

爬取

1
2
3
4
5
6
7
8
def crawl(self,login_url,cata_url):
self.login(login_url)#登陆
item_list=self.get_url_from_cata_all(cata_url)#获取所有标题以及对应链接
for i in item_list:
title,url=i#解包
text=self.get_page(url)#获取内容
self.save_by_txt(text,title+'.txt')#保存
#self.save_by_db(text,title)

调用

1
2
3
4
5
6
7
8
9
headers={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36',
}
login_url='http://xxx.xxx.xxx.cn/authserver/login?service=http%3A%2F%2Fportal.chd.edu.cn%2F'
cata_url='http://xxxxxx.xxx.xxx.cn/detach.portal?pageIndex=1&pageSize=&.pmn=view&.ia=false&action=bulletinsMoreView&search=true&groupid=all&.pen=pe65'

#调用
spiderman=spider(headers)
spiderman.crawl(login_url, cata_url)
作者

憧憬少

发布于

2019-03-15

更新于

2019-03-15

许可协议