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

前言

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

个人博客传送门:

  • python爬虫学习笔记1一个简单的爬虫
  • python爬虫学习笔记2模拟登录与数据库

由于本文比较长,建议前往我个人博客阅读有侧栏目录的版本:

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

这篇和前面两篇一样,是按照时间记录的。

转载注明出处。

任务介绍

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

先做第二个任务

过程记录

190310 周日

创建爬虫类

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()函数

参考链接:

  • python3-password在输入密码时隐藏密码-博客园
  • Python之控制台输入密码的方法-博客园
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

day8进度

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

190311 周一

day9进度

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

190312 周二

获取单页目录内的公告url

目录网页的内容:

  • 关于……的通知
  • 关于……获奖
  • ……
  • 2700条记录,分为138页显示,下一页
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

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 周三

获取正文

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

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

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 周四

cookie保持登录

参考链接:

  • Python——Cookie保存到本地-知乎(解决了问题的主要链接)
  • 爬虫保存cookies时重要的两个参数(ignore_discard和ignore_expires)的作用

首先是库

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')

保存cookie的函数

大概是将已登录的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)

加载cookie的函数

首先初始化一个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

修改后的login()

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

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

保存到txt

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))

保存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)

加载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:
        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)

你可能感兴趣的:(学习笔记,python,spider)