在完成了基本的爬取任务之后,接到了将其封装为一个爬虫类的任务
个人博客传送门:
由于本文比较长,建议前往我个人博客阅读有侧栏目录的版本:
python爬虫学习笔记3封装爬虫类
这篇和前面两篇一样,是按照时间记录的。
转载注明出处。
1、尝试不使用session去进行爬取,最好能将cookies保存下来可以供下次使用。
2、第二个是尝试将这些封装成面向对象的方式,模拟登陆,爬取,解析,写入数据库这几个部分分离开来。
先做第二个任务
class spider:
'''
爬虫类
'''
def __init__(self):
self.session=requests.session()#初始化登录session
self.is_login=False#登录状态
获取登录信息(账号密码以及校验码)这部分与登录可以分开,单独写一个成员函数。
在输入密码这个地方,本来查到可以使用getpass
这个库里面的getpass()
函数来使用类似linux的密码不回显,用法如下:
import getpass
passwd=getpass.getpass()
print(passwd)#测试用输出
但是直接在pycharm里面运行是会卡在输入那里,并且也会回显。后来查到了,这个方法是在命令行当中才管用,我试了一下在python命令行中使用,
>>>import getpass
>>>passwd=getpass.getpass()
Warning: Password input may be echoed.
Password: >? 123
>>>print(passwd)
123
虽然可以使用了,但是仍然会回显。所以这个命令行说的应该不是python命令行,而是cmd或者shell。
在虚拟环境的cmd里面,成功了,Password后面未回显我的输入,下面的数字是测试用的输出,将密码打印出来。
(venv) F:\DEVELOP\py_develop\spider>python test.py
Password:
123
不过为了方便调试代码,我还是使用了input()
函数
参考链接:
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
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
目录网页的内容:
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
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
实现了自动获取目录页数,并从每一页目录获取所有的url,返回当前所有公告的url的列表
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
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))
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))
尝试将保存到数据库的函数里面的数据库参数放到函数形参处,怎么弄都觉得不太合适,于是还是将原本的代码放入
参考链接:
首先是库
import http.cookiejar
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已经被抛弃和过期。
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对象
load_cookiejar = http.cookiejar.LWPCookieJar()
接着从文件中加载cookie
load_cookiejar.load('cookies', ignore_discard=True, ignore_expires=True)
这里有个问题,这里如果加载失败了(没有这个文件,之前没有保存),需要知道已经失败了。所以使用一个try语句块测试一下。
然后把这个LWPCookieJar对象给转换成字典,再转换赋值给session.cookie,这样就加载成功了
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
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
import requests
from bs4 import BeautifulSoup
import pymysql
import re
import http.cookiejar
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')
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
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
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
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
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
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))
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))
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)
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
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)
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)