Python网络爬虫实战(第2版)
上QQ阅读APP看书,第一时间看更新

2.3 函数和类

C、C++、Java、Ruby、Perl、Lisp……在笔者所知的编程语言之中,所有程序都是由函数(有的编程语言叫做过程、方法什么的)和类组成的。可以说任何程序里面包含的不是函数就是类,Python当然也不例外。

2.3.1 函数

在学习UNIX时,曾经有一句非常出名的话是In UNIX Everything Is A File,在UNIX中所有的一切都是文件。在这里可以借鉴一下,In Python Everything Is A Function,在Python程序中,所有的一切都是函数。这是典型的C语言写法,把所需的功能都写成一个个的函数,然后由函数调用函数。以此类推,最终完成整个程序的功能。

还记得前面提过的暴力破解吗?不管用什么工具,暴力破解都少不了一个合适的字典文件(此字典非彼字典,这里的字典指的是一个包含密码的文件,也就是一个密码集,而不是Python的变量类型)。当然网上有很多的密码字典可供下载,但它们要么太大,遍历一次需要太多的时间,要么没有针对性,根本就不包含所需的密码。如果已知了一些可能是密码的字符串,完全可以根据已知条件用程序编写有针对性的字典出来,这样会节省很多时间。

【示例2-13】现在来编写一个简单的程序mkPassFileFunction.py。

mkPassFileFunction.py,创建一个有针对性的专用密码字典。打开Putty连接到Linux,执行命令:

     cd code/crawler
     vi mkPassFileFunction.py

mkPassFileFunction.py的代码如下:

      1 #!/usr/bin/env python3
      2 #-*- coding: utf-8 -*-
      3 __author__ = 'hstking hst_king@hotmail.com'
      4
      5 import os
      6 import platform
      7 import itertools
      8 import time
      9
     10 def main():
     11     '''主程序 '''
     12     global rawList #原始数据列表
     13     rawList = []
     14     global denyList #非法单词列表
     15     denyList = [' ','','@']
     16     global pwList #最终的密码列表
     17     pwList = []
     18     global minLen #密码的最小长度
     19     minLen = 6
     20     global maxLen #密码的最大长度
     21     maxLen = 16
     22     global timeout
     23     timeout = 3
     24     global flag
     25     flag = 0
     26     run = {
     27 '0':exit,
     28 '1':getRawList,
     29 '2':addDenyList,
     30 '3':clearRawList,
     31 '4':setRawList,
     32 '5':modifyPasswordLen,
     33 '6':createPasswordList,
     34 '7':showPassword,
     35 '8':createPasswordFile
     36 }
     37
     38     while True:
     39         mainMenu()
     40         op = input('输入选项:')
     41         if op in map(str,range(len(run))):
     42             run.get(op)()
     43         else:
     44             tipMainMenuInputError()
     45             continue
     46
     47 def mainMenu():
     48     '''主菜单 '''
     49     global denyList
     50     global rawList
     51     global pwList
     52     global flag
     53     clear()
     54     print('||'),
     55     print('='*40),
     56     print('||')
     57     print('|| 0:退出程序')
     58     print('|| 1:输入密码原始字符串')
     59     print('|| 2:添加非法字符到列表')
     60     print('|| 3:清空原始密码列表')
     61     print('|| 4:整理原始密码列表')
     62     print('|| 5:改变默认密码长度(%d-%d)' %(minLen,maxLen))
     63     print('|| 6:创建密码列表')
     64     print('|| 7:显示所有密码')
     65     print('|| 8:创建字典文件')
     66     print('||'),
     67     print('='*40),
     68     print('||')
     69     print('当前非法字符为:%s' %denyList)
     70     print('当前原始密码元素为:%s' %rawList)
     71     print('共有密码%d个' %len(pwList))
     72     if flag:
     73         print("已在当前目录创建密码文件dic.txt")
     74     else:
     75         print("尚未创建密码文件")
     76
     77 def clear():
     78     '''清屏函数 '''
     79     OS = platform.system()
     80     if (OS == u'Windows'):
     81         os.system('cls')
     82     else:
     83         os.system('clear')
     84
     85 def tipMainMenuInputError():
     86     '''错误提示 '''
     87     clear()
     88     print("只能输入0-7的整数,等待%d秒后重新输入" %timeout)
     89     time.sleep(timeout)
     90
     91 def getRawList():
     92     '''获取原始数据列表 '''
     93     clear()
     94     global denyList
     95     global rawList
     96     print("输入回车后直接退出")
     97     print("当前原始密码列表为:%s" %rawList)
     98     st = None
     99     while not st == '':
     100         st = input("请输入密码元素字符串:")
     101         if st in denyList:
     102             print("这个字符串是预先设定的非法字符串")
     103             continue
     104         else:
     105             rawList.append(st)
     106             clear()
     107             print("输入回车后直接退出")
     108             print("当前原始密码列表为:%s" %rawList)
     109
     110 def addDenyList():
     111     '''添加非法词 '''
     112     clear()
     113     global denyList
     114     print("输入回车后直接退出")
     115     print("当前非法字符为:%s" %denyList)
     116     st = None
     117     while not st == '':
     118         st = input("请输入需要添加的非法字符串:")
     119         denyList.append(st)
     120         clear()
     121         print("输入回车后直接退出")
     122         print("当前非法字符列表为:%s" %denyList)
     123
     124 def clearRawList():
     125     '''清空原始数据列表 '''
     126     global rawList
     127     rawList = []
     128
     129 def setRawList():
     130     '''整理原始数据列表 '''
     131     global rawList
     132     global denyList
     133     a = set(rawList)
     134     b = set(denyList)
     135     rawList = []
     136     for str in set(a - b):
     137         rawList.append(str)
     138
     139 def modifyPasswordLen():
     140     '''修改默认密码的长度 '''
     141     clear()
     142     global maxLen
     143     global minLen
     144     while True:
     145         print("当前密码长度为%d-%d" %(minLen,maxLen))
     146         min = input("请输入密码最小长度:")
     147         max = input("请输入密码最大长度:")
     148         try:
     149             minLen = int(min)
     150             maxLen = int(max)
     151         except ValueError:
     152             print("密码长度只能输入数字[6-18]")
     153             break
     154         if minLen not in range(6,19) or  maxLen not in range(6,19):
     155             print("密码长度只能输入数字[6-18]")
     156             minLen = 6
     157             maxLen = 16
     158             continue
     159         if minLen == maxLen:
     160             res = input("确定将密码长度设定为%d吗?(Yy/Nn)" %minLen)
     161             if res not in list('yYnN'):
     162                 print("输入错误,请重新输入")
     163                 continue
     164             elif res in list('yY'):
     165                 print("好吧,你确定就好")
     166                 break
     167             else:
     168                 print("给个机会,改一下吧")
     169                 continue
     170         elif minLen > maxLen:
     171             print("最小长度比最大长度还大,可能吗?请重新输入")
     172             minLen = 6
     173             maxLen = 16
     174             continue
     175         else:
     176             print("设置完毕,等待%d秒后回主菜单" %timeout)
     177             time.sleep(timeout)
     178             break
     179
     180 def createPasswordList():
     181     '''创建密码列表 '''
     182     global rawList
     183     global pwList
     184     global maxLen
     185     global minLen
     186     titleList = []
     187     swapcaseList = []
     188     for st in rawList:
     189         swapcaseList.append(st.swapcase())
     190         titleList.append(st.title())
     191     sub1 = []
     192     sub2 = []
     193     for st in set(rawList + titleList + swapcaseList):
     194         sub1.append(st)
     195     for i in range(2,len(sub1) + 1):
     196         sub2 += list(itertools.permutations(sub1,i))
     197     for tup in sub2:
     198         PW = ''
     199         for subPW in tup:
     200             PW += subPW
     201         if len(PW) in range(minLen,maxLen + 1):
     202             pwList.append(PW)
     203         else:
     204             pass
     205
     206 def showPassword():
     207     '''显示创建的密码 '''
     208     global pwList
     209     global timeout
     210     for i in range(len(pwList)):
     211         if i%4 == 0:
     212             print("%s\n" %pwList[i])
     213         else:
     214             print("%s\t" %pwList[i]),
     215     print('\n')
     216     print("显示%d秒,回到主菜单" %timeout)
     217     time.sleep(timeout)
     218
     219 def createPasswordFile():
     220     '''创建密码字典文件 '''
     221     global flag
     222     global pwList
     223     print("当前目录下创建字典文件:dic.txt")
     224     time.sleep(timeout)
     225     with open('./dic.txt','w+') as fp:
     226         for PW in pwList:
     227             fp.write(PW)
     228             fp.write('\n')
     229     flag = 1
     230
     231
     232 if __name__ == '__main__':
     233     main()

按Esc键,进入命令模式后输入:wq,保存mkPassFileFunction.py。mkPassFileFunction.py稍微复杂一点点,它的作用就是根据用户输入的“密码元素”来创建一个字典列表。该脚本将输入的元素根据一定的规则修改、添加后当作新元素添加到元素列表中去。最后将元素列表排列组合得到字典列表。执行命令:

     python mkPassFileFunction.py

得到的结果如图2-19所示。

图2-19 运行mkPassFileFunction.py

纯C语言的写法好处就是关系简单明了,函数调用一目了然。如果调用的函数过多,就难免有些混乱了。简单功能的程序还无妨,稍大一点项目就有些吃力了。

提示

不要添加太多的“密码元素”,这个程序只是利用了Python 3的模块,没有优化算法。如果输入的“密码元素”超过了20个,那么创建密码字典的时间会非常长。

2.3.2 类

既然有了In Python Everything Is A Function,当然会有In Python Everything Is A Class。这种C++的写法就是把所有相似的功能都封装到一个类里。最理想的情况是一个程序只有一个主程序,然后在主程序里实例化类。

【示例2-14】还是以编写密码字典为例,将mkPassFileFunction.py改编成mkPassFileClass.py。打开Putty连接到Linux,执行命令:

     cd code/crawler
     vi mkPassFileClass.py

mkPassFileClass.py的代码如下:

      1 #!/usr/bin/env python3
      2 #-*- coding: utf-8 -*-
      3 __author__ = 'hstking hst_king@hotmail.com'
      4
      5 import os
      6 import platform
      7 import itertools
      8 import time
      9
     10 class MakePassword(object):
     11     def __init__(self):
     12         self.rawList = []
     13         self.denyList = ['',' ','@']
     14         self.pwList = []
     15         self.minLen = 6
     16         self.maxLen = 16
     17         self.timeout = 3
     18         self.flag = 0
     19         self.run = {
     20 '0':exit,
     21 '1':self.getRawList,
     22 '2':self.addDenyList,
     23 '3':self.clearRawList,
     24 '4':self.setRawList,
     25 '5':self.modifyPasswordLen,
     26 '6':self.createPasswordList,
     27 '7':self.showPassword,
     28 '8':self.createPasswordFile
     29 }
     30         self.main()
     31
     32     def main(self):
     33         while True:
     34             self.mainMenu()
     35             op = input('输入选项:')
     36          if op in map(str,range(len(self.run))):
     37              self.run.get(op)()
     38          else:
     39              self.tipMainMenuInputError()
     40              continue
     41
     42  def mainMenu(self):
     43      self.clear()
     44      print('||'),
     45      print('='*40),
     46      print('||')
     47      print('|| 0:退出程序')
     48      print('|| 1:输入密码原始字符串')
     49      print('|| 2:添加非法字符到列表')
     50      print('|| 3:清空原始密码列表')
     51      print('|| 4:整理原始密码列表')
     52      print('|| 5:改变默认密码长度(%d-%d)' %(self.minLen,self.maxLen))
     53      print('|| 6:创建密码列表')
     54      print('|| 7:显示所有密码')
     55      print('|| 8:创建字典文件')
     56      print('||'),
     57      print('='*40),
     58      print('||')
     59      print('当前非法字符为:%s' %self.denyList)
     60      print('当前原始密码元素为:%s' %self.rawList)
     61      print('共有密码%d个' %len(self.pwList))
     62      if self.flag:
     63          print("已在当前目录创建密码文件dic.txt")
     64      else:
     65          print("尚未创建密码文件")
     66
     67  def clear(self):
     68      OS = platform.system()
     69      if (OS == u'Windows'):
     70          os.system('cls')
     71      else:
     72          os.system('clear')
     73
     74  def tipMainMenuInputError(self):
     75      self.clear()
     76      print("只能输入0-7的整数,等待%d秒后重新输入" %timeout)
     77      time.sleep(timeout)
     78
     79   def getRawList(self):
     80       self.clear()
     81       print("输入回车后直接退出")
     82       print("当前原始密码列表为:%s" %self.rawList)
     83       st = None
     84       while not st == '':
     85           st = input("请输入密码元素字符串:")
     86           if st in self.denyList:
     87               print("这个字符串是预先设定的非法字符串")
     88               continue
     89           else:
     90               self.rawList.append(st)
     91               self.clear()
     92               print("输入回车后直接退出")
     93               print("当前原始密码列表为:%s" %self.rawList)
     94
     95   def addDenyList(self):
     96       self.clear()
     97       print("输入回车后直接退出")
     98       print("当前非法字符为:%s" %self.denyList)
     99       st = None
     100       while not st == '':
     101           st = input("请输入需要添加的非法字符串:")
     102           self.denyList.append(st)
     103           self.clear()
     104           print("输入回车后直接退出")
     105           print("当前非法字符列表为:%s" %self.denyList)
     106
     107   def clearRawList(self):
     108       self.rawList = []
     109
     110   def setRawList(self):
     111       a = set(self.rawList)
     112       b = set(self.denyList)
     113       self.rawList = []
     114       for str in set(a - b):
     115           self.rawList.append(str)
     116
     117   def modifyPasswordLen(self):
     118       self.clear()
     119       while True:
     120           print("当前密码长度为%d-%d" %(self.minLen,self.maxLen))
     121           min = input("请输入密码最小长度:")
     122          max = input("请输入密码最大长度:")
     123          try:
     124              self.minLen = int(min)
     125              self.maxLen = int(max)
     126          except ValueError:
     127              print("密码长度只能输入数字[6-18]")
     128              break
     129         if self.minLen not in range(6,19) or self.maxLen not in range(6,19):
     130              print("密码长度只能输入数字[6-18]")
     131              self.minLen = 6
     132              self.maxLen = 16
     133              continue
     134         if self.minLen == self.maxLen:
     135              res = input("确定将密码长度设定为%d吗?(Yy/Nn)" %self.minLen)
     136              if res not in list('yYnN'):
     137                  print("输入错误,请重新输入")
     138                  continue
     139              elif res in list('yY'):
     140                  print("好吧,你确定就好")
     141                  break
     142              else:
     143                  print("给个机会,改一下吧")
     144                  continue
     145          elif self.minLen > self.maxLen:
     146              print("最小长度比最大长度还大,可能吗?请重新输入")
     147              self.minLen = 6
     148              self.maxLen = 16
     149              continue
     150          else:
     151              print("设置完毕,等待%d秒后回主菜单" %self.timeout)
     152              time.sleep(self.timeout)
     153              break
     154
     155  def createPasswordList(self):
     156      titleList = []
     157      swapcaseList = []
     158      for st in self.rawList:
     159          swapcaseList.append(st.swapcase())
     160          titleList.append(st.title())
     161      sub1 = []
     162      sub2 = []
     163      for st in set(self.rawList + titleList + swapcaseList):
     164          sub1.append(st)
     165         for i in range(2,len(sub1) + 1):
     166             sub2 += list(itertools.permutations(sub1,i))
     167         for tup in sub2:
     168             PW = ''
     169             for subPW in tup:
     170                 PW += subPW
     171             if len(PW) in range(self.minLen,self.maxLen + 1):
     172                 self.pwList.append(PW)
     173             else:
     174                 pass
     175
     176     def showPassword(self):
     177         for i in range(len(self.pwList)):
     178             if i%4 == 0:
     179                 print("%s\n" %self.pwList[i])
     180             else:
     181                 print("%s\t" %self.pwList[i]),
     182         print('\n')
     183         print("显示%d秒,回到主菜单" %self.timeout)
     184         time.sleep(self.timeout)
     185
     186     def createPasswordFile(self):
     187         print("当前目录下创建字典文件:dic.txt")
     188         time.sleep(self.timeout)
     189         with open('./dic.txt','w+') as fp:
     190             for PW in self.pwList:
     191                 fp.write(PW)
     192                 fp.write('\n')
     193         self.flag = 1
     194
     195
     196 if __name__ == '__main__':
     197     mp = MakePassword()

按Esc键,进入命令模式后输入:wq,保存mkPassFileClass.py。mkPassFileClass.py和mkPassFileFunction.py实质上没有什么区别,只是一个使用的是C语言风格的函数调用,一个使用的是C++风格的类实例化。执行命令:

     python3 mkPassFileClass.py

得到的结果如图2-20所示。

图2-20 运行mkPassFileClass.py

执行结果完全一样。这种C++的写法好处就是调用过程简单,不再关心类具体的实现过程,只需要调用其功能即可;但随之而来就是类的继承、函数重载等麻烦。这种写法在写大项目时可能非常有用,写小程序也行,只是没有那么多优势了。

提示

这个程序还有一个问题,就是在创建密码文件前并没有估算磁盘剩余空间是否足够。一般的解决办法是先估算密码文件的大小,然后创建一个大小相同的空文件,能创建成功就继续运行程序,不能则抛出异常。