用python必须学会使用类方法,使用类方法和普通方法的优点在于,类方法可以极大程度的减少代码复用,模式清晰,而且可以将代码封装,方便多次使用,还有诸多类似继承等方法。在有一定代码编写基础后,推荐使用类方法来编写,也就是面向对象编程。
1面向对象编程
面向对象相对于普通面向过程编程的区别在于面向对象方法不是只针对一个个体进行编程,而是针对一群对象的属性来编程。
打个比方,你想去吃面。
面向过程:买面条,洗锅,烧水,放调料,煮面条,吃面。
面向对象:进商店,点菜,吃面。
这两个方法都完成了吃面这个操作,但是区别在于面向过程你需要将每一步都描述清楚,而使用面向对象方法,你不需要去管这个函数如何实现,我只需要得到结果就行了,在这个例子中就是我不需要知道商店的面是怎么做的,我能吃到面就行了。就像我们平常调用函数,一般不会去管它如何实现,我只需要调用了,得到结果就ok。
而我们平常自己写类方法,我们需要先用面向过程来打草稿,让我们知道,我们需要定义对象的几个属性才能完成这次操作,或许有些时候直接写面向过程会很简单,但是可读性,可改性,复用性会很差。
面向过程 | 面向对象 |
---|---|
变量和函数散落在文件的各个位置,看不出变量和函数的相关性,不利于维护,涉及模式不清晰,且经常导致忘记某一个变量的相关性,而无法检测错误 | 相关变量和函数都封装在对象里,以对象为单位来管理代码。变量与函数相关性清晰,利于维护。可以使用继承来提高代码的可复用性,加强合作开发。 |
总结:
用面向对象的思维去解决问题,将某一类事务相关的属性和方法封装在一起,用来表示这类事物。
2使用面向对象方法实例
from selenium import webdriver
from lxml import etree
import time
import re
class DySpider(object):
def __init__(self):
self.driver = webdriver.Chrome()
# 最大化窗口
self.driver.maximize_window()
self.start_url = 'https://www.douyu.com/directory/all'
self.driver.get('https://passport.douyu.com/member/login?')
time.sleep(10)
def parse_url(self):
"""
关闭弹窗,执行滑动
:return:html_element(elements内容), num(最大页码数)
"""
self.driver.get(self.start_url)
try:
"""访问后,没有弹窗,在此使用try嵌套"""
time.sleep(3)
self.driver.find_element_by_class_name('ZoomTip-tipHide').click()
except:
"""没有弹窗,则pass"""
pass
"""获取elements内容"""
time.sleep(2)
self.page_url()
html_element = self.driver.page_source
print(html_element)
"""提取翻页的最大页码数"""
num = re.findall('dy-Pagination-item dy-Pagination-item-(\d+)',html_element)[5]
"""验证是否正常(成功)获取"""
print('返回的页面共有:' + num)
return num
def page_url(self, num=None):
"""
构造向下滑动
:return:
"""
if num is None:
num = 10000
"""每次向下滑动距离10000"""
js = 'scrollTo(0, {})'.format(num)
"""执行滑动"""
self.driver.execute_script(js)
"""滑动之后沉睡等待2秒"""
time.sleep(2)
def parse_page_url(self, num):
"""
执行翻页
:param num: 页码数
"""
"""根据最大页码数的遍历,来执行点击下一页的次数"""
for i in range(int(num) - 2):
"""调用滑动函数"""
self.page_url()
time.sleep(2)
"""下一页的点击事件"""
self.driver.find_element_by_css_selector('#listAll > section.layout-Module.js-ListContent > div.layout-Module-container.layout-Cover.ListContent > div > ul > li.dy-Pagination-next > span').click()
"""每次成功下一页之后,获取其elements内容"""
html_data = self.driver.page_source
html_element = etree.HTML(html_data)
self.save_id_data(html_element)
def save_id_data(self, html_element):
"""
:param html_element: etree转换之后的对象
"""
# 获取直播间的id列表
# html_element = etree.HTML(html_element)
id_num = html_element.xpath('//*[@id="listAll"]/section[2]/div[2]/ul/li/div/a[1]/@href')
# 直播间id去重
# id_num = list(set(id_num))
print(id_num)
# 访问第一个直播间,达到去除窗口的效果
js = 'window.open("{}");'.format('https://www.douyu.com' + id_num[0])
self.driver.execute_script(js)
time.sleep(2)
# 获取所有浏览器窗口
windows = self.driver.window_handles
# 窗口切换,切换到新打开的直播间窗口
self.driver.switch_to.window(windows[1])
# 关闭打开的窗口
self.driver.close()
# 切换到初始窗口
self.driver.switch_to.window(windows[0])
for id in id_num:
# 直播间的url地址拼接
url = 'https://www.douyu.com' + id
# 调用函数
self.requests_url_by(url)
# print(url)
def requests_url_by(self, url):
"""
访问直播
:param url: 直播间的url地址
"""
# js代码执行打开url--直播间的url地址
js = 'window.open("{}");'.format(url)
self.driver.execute_script(js)
# 隐式等待
self.driver.implicitly_wait(10)
# 获取当前所有窗口,返回一个列表
windows = self.driver.window_handles
# 窗口切换,下标操作
self.driver.switch_to.window(windows[1])
try:
for i in range(1, 3):
text = '1'
self.driver.find_element_by_css_selector('.ChatSend-txt').send_keys(text) # 输入弹幕
self.driver.find_element_by_css_selector('.ChatSend-button ').click() # 点击发送弹幕
print(f'弹幕----------已发送----------第{i}次')
time.sleep(1.5) # 每一秒发送一次共发送20次
except:
print('发送失败')
self.driver.close()
self.driver.switch_to.window(windows[0])
def run(self):
num = self.parse_url()
# self.save_id_data(html_element)
self.parse_page_url(num)
if __name__ == '__main__':
dy = DySpider()
dy.run()
在讲解代码之前,我们需要明白,当我们开始编写代码时,就说这个爬虫,我们需要用哪些步骤来完成这个爬虫。
步骤 | 解释 |
---|---|
第一步 | 我们知道想在斗鱼发送弹幕,我们需要登陆,那么怎么办呢,最简单的方法就是在弹出selenium操纵的chrome网页中扫码登陆,而且登陆之后会直接进入斗鱼首界面 |
第二步 | 我们需要找到一个页面,这个页面有所有已开播房间的信息,也就是https://www.douyu.com/directory/all,这种信息需要自己寻找,不会有人告诉你。 |
第三步 | 我们需要在网页中遍历所有的房间号,然后通过房间号来构造完整的网址 |
第四步 | 在访问房间后,有一些房间是需要往下翻才能找到输入弹幕口的,因为是动态渲染的。所以我们需要将网页翻到最下面将输入口渲染出来,以免报错。 |
那么在这些步骤中我们有一个很清晰的树状结构,以斗鱼为树干,遍历一页的房间为树枝,获取一页中每一个房间为一个树枝上的叶子。有了思路之后,我们来面向对象编程。
首先,我们将不变量写出来,初始网址(即登陆网址),含有房间页面的网址,这些东西就可以放在最开始的 __init__函数中.
然后我们按照我们的思路,我们照这个思路,就可以使用遍历方法,一层一层嵌套。当然这需要你熟练掌握类方法之后才会有清晰的思路。
获取房间的总页数,然后遍历每一页,在遍历每一页的函数中调用获取房间号的函数,再在获取房间号的函数中调用访问房间,发出弹幕的函数,那么整个爬虫就做完了。
那么经过我们的思路,我们只需要将每一个函数都写出来,然后在每一个函数之间建立联系,就一步一步完成了整个爬取。
class DySpider(object):
def __init__(self):
self.driver = webdriver.Chrome()
# 最大化窗口
self.driver.maximize_window()
self.start_url = 'https://www.douyu.com/directory/all'
self.driver.get('https://passport.douyu.com/member/login?')
time.sleep(10)
这是我们的第一步,创建一个类,我们就叫DySpider,然后它会自动传入一个对象,object。首先定义初始化方法,我们可以将不变的量放入其中,也就是全部房间的网址,而在类方法中,我们需要将driver方法传递给其他方法的话,就必须带上self前缀,也就是本身,如果不加self,就只有这个函数能用该方法。也就是所谓的作用域问题。这里sleep10秒是为了你拿出手机登陆准备的时间。
def parse_url(self):
"""
关闭弹窗,执行滑动
:return:html_element(elements内容), num(最大页码数)
"""
self.driver.get(self.start_url)
try:
"""访问后,没有弹窗,在此使用try嵌套"""
time.sleep(3)
self.driver.find_element_by_class_name('ZoomTip-tipHide').click()
except:
"""没有弹窗,则pass"""
pass
"""获取elements内容"""
time.sleep(2)
self.page_url()
html_element = self.driver.page_source
print(html_element)
"""提取翻页的最大页码数"""
num = re.findall('dy-Pagination-item dy-Pagination-item-(\d+)',html_element)[5]
"""验证是否正常(成功)获取"""
print('返回的页面共有:' + num)
return num
第一个初始化函数中我们完成了登陆的操作,现在就需要在全部房间的网址中获取房间总页数。这里我写的去掉弹窗的问题可有可无,不影响发弹幕,只是看上去好观察现象一些。这里我们获取到房间总页数之后,将它返回出去。
def page_url(self, num=None):
"""
构造向下滑动
:return:
"""
if num is None:
num = 10000
"""每次向下滑动距离10000"""
js = 'scrollTo(0, {})'.format(num)
"""执行滑动"""
self.driver.execute_script(js)
"""滑动之后沉睡等待2秒"""
time.sleep(2)
这里我们需要定义一个将网页滑到最下面的方法,顺便一提,这里的num和上一个函数定义的num可不一样,也不是传入这个函数的属性。
def parse_page_url(self, num):
"""
执行翻页
:param num: 页码数
"""
"""根据最大页码数的遍历,来执行点击下一页的次数"""
for i in range(int(num) - 2):
"""调用滑动函数"""
self.page_url()
time.sleep(2)
"""下一页的点击事件"""
self.driver.find_element_by_css_selector('#listAll > section.layout-Module.js-ListContent > div.layout-Module-container.layout-Cover.ListContent > div > ul > li.dy-Pagination-next > span').click()
"""每次成功下一页之后,获取其elements内容"""
html_data = self.driver.page_source
html_element = etree.HTML(html_data)
self.save_id_data(html_element)
这里我们传入房间总页数,然后用for i in range(int(num) - 2)遍历每一页,然后在每一页的遍历中调用save_id_data方法,也就是下一个我们定义的方法,即获取每一页的房间的房间号。
def save_id_data(self, html_element):
"""
:param html_element: etree转换之后的对象
"""
# 获取直播间的id列表
# html_element = etree.HTML(html_element)
id_num = html_element.xpath('//*[@id="listAll"]/section[2]/div[2]/ul/li/div/a[1]/@href')
# 直播间id去重
# id_num = list(set(id_num))
print(id_num)
# 访问第一个直播间,达到去除窗口的效果
js = 'window.open("{}");'.format('https://www.douyu.com' + id_num[0])
self.driver.execute_script(js)
time.sleep(2)
# 获取所有浏览器窗口
windows = self.driver.window_handles
# 窗口切换,切换到新打开的直播间窗口
self.driver.switch_to.window(windows[1])
# 关闭打开的窗口
self.driver.close()
# 切换到初始窗口
self.driver.switch_to.window(windows[0])
for id in id_num:
# 直播间的url地址拼接
url = 'https://www.douyu.com' + id
# 调用函数
self.requests_url_by(url)
# print(url)
这里有很多我打的草稿,只看有效部分就好,这里我们传入的是上一个函数中的页面的经过etree.HTML处理的源代码,也就是xpath对象。这样我们就可以调用xpath方法来解析获取所有房间号,然后在这个方法中将获取的房间号遍历,拼接程完整的网址后,调用下一个方法,在调用下一个方法时,都需要在前面加上self.,这是函数内部传递信息的方法。
def requests_url_by(self, url):
"""
访问直播
:param url: 直播间的url地址
"""
# js代码执行打开url--直播间的url地址
js = 'window.open("{}");'.format(url)
self.driver.execute_script(js)
# 隐式等待
self.driver.implicitly_wait(10)
# 获取当前所有窗口,返回一个列表
windows = self.driver.window_handles
# 窗口切换,下标操作
self.driver.switch_to.window(windows[1])
try:
for i in range(1, 3):
text = '1'
self.driver.find_element_by_css_selector('.ChatSend-txt').send_keys(text) # 输入弹幕
self.driver.find_element_by_css_selector('.ChatSend-button ').click() # 点击发送弹幕
print(f'弹幕----------已发送----------第{i}次')
time.sleep(1.5) # 每一秒发送一次共发送20次
except:
print('发送失败')
self.driver.close()
self.driver.switch_to.window(windows[0])
这里传入上一个函数拼接的完整网址,然后使用try方法来进行断言,也就是说如果没有完整完成try方法中的执行步骤,就会输出发送错误字样,然后在每一个房间完成弹幕发送后,就关闭房间,然后将可视的界面切换到斗鱼首页。
def run(self):
num = self.parse_url()
# self.save_id_data(html_element)
self.parse_page_url(num)
我们可以发现,上述的parse_url方法中返回的num在代码中并没有被任意函数接收,我们需要使用run方法来完成这一个步骤。这个run方法也相当于整个项目的发动机,我们只需要在外部创建这个类的一个对象之后,直接调用对象的run()方法,就可以将整个方法跑起来。
if __name__ == '__main__':
dy = DySpider()
dy.run()
这里的if name == ‘main’:是指只在该文件下可调用,然后创建该方法的一个对象,然后调用对象的run()方法,就可以完成整个操作了。
整个代码讲述完毕,这样看上去是不是过程非常清晰,可读性很高。
3小结
类方法的内核方法和知识点非常多,会写类方法只是九牛一毛,在python参考手册中,讲述的类方法,说实在的,看不懂。但是现阶段,会使用类方法来编程就可以了。