春运返程高峰,用Python分析12306抢票、自动刷票的案例

时间真快,春节假期已到了最后一天,想必有不少人都已踏上了返程的旅途。21日是节假日的最后一天,一些大城市的返程交通也将迎来拥堵高峰。登录12306购票网站点进去,买最近几天的火车票,显示都已卖完,今天小编用Python分析12306抢票,自动刷票的案例,供大家学习交流,代码如下:

上面是python爬取火车票的信息,我们知道Python要想获取网页信息之前,必须要学习一下Python正则,下面分享小编分享一下Python正则表达式模块相关知识:

1. Python正则表达式模块

 1.1 正则表达式处理字符串主要有四大功能

    1. 匹配 查看一个字符串是否符合正则表达式的语法,一般返回true或者false

    2. 获取正则表达式来提取字符串中符合要求的文本

    3. 替换查找字符串中符合正则表达式的文本,并用相应的字符串替换

    4. 分割使用正则表达式对字符串进行分割。

1.2 Python中re模块使用正则表达式的两种方法

    1. 使用re.compile(r, f)方法生成正则表达式对象,然后调用正则表达式对象的相应方法。这种做法的好处是生成正则对象之后可以多次使用。

    2. re模块中对正则表达式对象的每个对象方法都有一个对应的模块方法,唯一不同的是传入的第一个参数是正则表达式字符串。此种方法适合于只使用一次的正则表达式。

1.3 正则表达式对象的常用方法

    1. rx.findall(s,start, end):

      返回一个列表,如果正则表达式中没有分组,则列表中包含的是所有匹配的内容,

      如果正则表达式中有分组,则列表中的每个元素是一个元组,元组中包含子分组中匹配到的内容,但是没有返回整个正则表达式匹配的内容

    2. rx.finditer(s, start, end):

      返回一个可迭代对象

      对可迭代对象进行迭代,每一次返回一个匹配对象,可以调用匹配对象的group()方法查看指定组匹配到的内容,0表示整个正则表达式匹配到的内容

    3. rx.search(s, start, end):

      返回一个匹配对象,倘若没匹配到,就返回None

      search方法只匹配一次就停止,不会继续往后匹配

    4. rx.match(s, start, end):

      如果正则表达式在字符串的起始处匹配,就返回一个匹配对象,否则返回None

    5. rx.sub(x, s, m):

      返回一个字符串。每一个匹配的地方用x进行替换,返回替换后的字符串,如果指定m,则最多替换m次。对于x可以使用/i或者/gid可以是组名或者编号来引用捕获到的内容。

      模块方法re.sub(r, x, s, m)中的x可以使用一个函数。此时我们就可以对捕获到的内容推过这个函数进行处理后再替换匹配到的文本。

    6. rx.subn(x, s, m):

      与re.sub()方法相同,区别在于返回的是二元组,其中一项是结果字符串,一项是做替换的个数。

    7. rx.split(s, m):分割字符串

      返回一个列表

      用正则表达式匹配到的内容对字符串进行分割

      如果正则表达式中存在分组,则把分组匹配到的内容放在列表中每两个分割的中间作为列表的一部分,如:

      rx = re.compile(r"(\d)[a-z]+(\d)")

      s = "ab12dk3klj8jk9jks5"

      result = rx.split(s)

      返回['ab1', '2', '3', 'klj', '8', '9', 'jks5']

    8. rx.flags():正则表达式编译时设置的标志

    9. rx.pattern():正则表达式编译时使用的字符串

 1.4 匹配对象的属性与方法

    01. m.group(g, ...)

      返回编号或者组名匹配到的内容,默认或者0表示整个表达式匹配到的内容,如果指定多个,就返回一个元组

    02. m.groupdict(default)

      返回一个字典。字典的键是所有命名的组的组名,值为命名组捕获到的内容

      如果有default参数,则将其作为那些没有参与匹配的组的默认值。

    03. m.groups(default)

      返回一个元组。包含所有捕获到内容的子分组,从1开始,如果指定了default值,则这个值作为那些没有捕获到内容的组的值

    04. m.lastgroup()

      匹配到内容的编号最高的捕获组的名称,如果没有或者没有使用名称则返回None(不常用)

    05. m.lastindex()

      匹配到内容的编号最高的捕获组的编号,如果没有就返回None。

    06. m.start(g):

      当前匹配对象的子分组是从字符串的那个位置开始匹配的,如果当前组没有参与匹配就返回-1

    07. m.end(g)

      当前匹配对象的子分组是从字符串的那个位置匹配结束的,如果当前组没有参与匹配就返回-1

    08. m.span()

      返回一个二元组,内容分别是m.start(g)和m.end(g)的返回值

    09. m.re()

      产生这一匹配对象的正则表达式

    10. m.string()

      传递给match或者search用于匹配的字符串

    11. m.pos()

      搜索的起始位置。即字符串的开头,或者start指定的位置(不常用)

    12. m.endpos()

      搜索的结束位置。即字符串的末尾位置,或者end指定的位置(不常用)

2、函数 (参见 python 模块 re 文档)

python 的 re 模块提供了很多方便的函数使你可以使用正则表达式来操作字符串,每种函数都有它自己的特性和使用场景,熟悉之后对你的工作会有很大帮助

    • compile(pattern, flags=0)

给定一个正则表达式 pattern,指定使用的模式 flags 默认为0 即不使用任何模式,然后会返回一个 SRE_Pattern (参见 第四小节 re 内置对象用法) 对象

regex = re.compile(".+")print regex# output> <_sre.SRE_Pattern object at 0x00000000026BB0B8>

这个对象可以调用其他函数来完成匹配,一般来说推荐使用 compile 函数预编译出一个正则模式之后再去使用,这样在后面的代码中可以很方便的复用它,当然大部分函数也可以不用 compile 直接使用,具体见 findall 函数

处输入图片描述

s = '''first linesecond linethird line'''#regex = re.compile(".+")# 调用 findall 函数print regex.findall(s)# output> ['first line', 'second line', 'third line']# 调用 search 函数print regex.search(s).group()# output> first lin
    • escape(pattern)

转义 如果你需要操作的文本中含有正则的元字符,你在写正则的时候需要将元字符加上反斜扛 \ 去匹配自身, 而当这样的字符很多时,写出来的正则表达式就看起来很乱而且写起来也挺麻烦的,这个时候你可以使用这个函数,用法如下

s = ".+\d123"#regex_str = re.escape(".+\d123")# 查看转义后的字符print regex_str# output> \.\+\\d123# 查看匹配到的结果for g in re.findall(regex_str, s):print g# output> .+\d123
  • findall(pattern, string, flags=0)

参数 pattern 为正则表达式, string 为待操作字符串, flags 为所用模式,函数作用为在待操作字符串中寻找所有匹配正则表达式的字串,返回一个列表,如果没有匹配到任何子串,返回一个空列表。

s = '''first linesecond linethird line'''# compile 预编译后使用 findallregex = re.compile("\w+")print regex.findall(s)# output> ['first', 'line', 'second', 'line', 'third', 'line']# 不使用 compile 直接使用 findallprint re.findall("\w+", s)# output> ['first', 'line', 'second', 'line', 'third', 'line']
  • finditer(pattern, string, flags=0)

参数和作用与 findall 一样,不同之处在于 findall 返回一个列表, finditer 返回一个迭代器(参见 http://www.cnblogs.com/huxi/archive/2011/07/01/2095931.html ), 而且迭代器每次返回的值并不是字符串,而是一个 SRE_Match (参见 第四小节 re 内置对象用法) 对象,这个对象的具体用法见 match 函数。

s = '''first linesecond linethird line'''regex = re.compile("\w+")print regex.finditer(s)# output> for i in regex.finditer(s):print i# output> <_sre.SRE_Match object at 0x0000000002B7A920># <_sre.SRE_Match object at 0x0000000002B7A8B8># <_sre.SRE_Match object at 0x0000000002B7A920># <_sre.SRE_Match object at 0x0000000002B7A8B8># <_sre.SRE_Match object at 0x0000000002B7A920># <_sre.SRE_Match object at 0x0000000002B7A8B8>
    • match(pattern, string, flags=0)

使用指定正则去待操作字符串中寻找可以匹配的子串, 返回匹配上的第一个字串,并且不再继续找,需要注意的是 match 函数是从字符串开始处开始查找的,如果开始处不匹配,则不再继续寻找,返回值为 一个 SRE_Match (参见 第四小节 re 内置对象用法) 对象,找不到时返回 None

s = '''first linesecond linethird line'''# compileregex = re.compile("\w+")m = regex.match(s)print m# output> <_sre.SRE_Match object at 0x0000000002BCA8B8>print m.group()# output> first# s 的开头是 "f", 但正则中限制了开始为 i 所以找不到regex = re.compile("^i\w+")print regex.match(s)# output> None
    • purge()

当你在程序中使用 re 模块,无论是先使用 compile 还是直接使用比如 findall 来使用正则表达式操作文本,re 模块都会将正则表达式先编译一下, 并且会将编译过后的正则表达式放到缓存中,这样下次使用同样的正则表达式的时候就不需要再次编译, 因为编译其实是很费时的,这样可以提升效率,而默认缓存的正则表达式的个数是 100, 当你需要频繁使用少量正则表达式的时候,缓存可以提升效率,而使用的正则表达式过多时,缓存带来的优势就不明显了 (参考 《python re.compile对性能的影响》http://blog.trytofix.com/article/detail/13/), 这个函数的作用是清除缓存中的正则表达式,可能在你需要优化占用内存的时候会用到。

    • search(pattern, string, flags=0)

函数类似于 match,不同之处在于不限制正则表达式的开始匹配位置

s = '''first linesecond linethird line'''# 需要从开始处匹配 所以匹配不到print re.match('i\w+', s)# output> None# 没有限制起始匹配位置print re.search('i\w+', s)# output> <_sre.SRE_Match object at 0x0000000002C6A920>print re.search('i\w+', s).group()# output> irst
    • split(pattern, string, maxsplit=0, flags=0)

参数 maxsplit 指定切分次数, 函数使用给定正则表达式寻找切分字符串位置,返回包含切分后子串的列表,如果匹配不到,则返回包含原字符串的一个列表

s = '''first 111 linesecond 222 linethird 333 line'''# 按照数字切分print re.split('\d+', s)# output> ['first ', ' line\nsecond ', ' line\nthird ', ' line']# \.+ 匹配不到 返回包含自身的列表print re.split('\.+', s, 1)# output> ['first 111 line\nsecond 222 line\nthird 333 line']# maxsplit 参数print re.split('\d+', s, 1)# output> ['first ', ' line\nsecond 222 line\nthird 333 line']
    • sub(pattern, repl, string, count=0, flags=0)

替换函数,将正则表达式 pattern 匹配到的字符串替换为 repl 指定的字符串, 参数 count 用于指定最大替换次数

s = "the sum of 7 and 9 is [7+9]."# 基本用法 将目标替换为固定字符串print re.sub('\[7\+9\]', '16', s)# output> the sum of 7 and 9 is 16.# 高级用法 1 使用前面匹配的到的内容 \1 代表 pattern 中捕获到的第一个分组的内容print re.sub('\[(7)\+(9)\]', r'\2\1', s)# output> the sum of 7 and 9 is 97.# 高级用法 2 使用函数型 repl 参数, 处理匹配到的 SRE_Match 对象def replacement(m): p_str = m.group() if p_str == '7': return '77' if p_str == '9': return '99' return ''print re.sub('\d', replacement, s)# output> the sum of 77 and 99 is [77+99].# 高级用法 3 使用函数型 repl 参数, 处理匹配到的 SRE_Match 对象 增加作用域 自动计算scope = {}example_string_1 = "the sum of 7 and 9 is [7+9]."example_string_2 = "[name = 'Mr.Gumby']Hello,[name]"def replacement(m): code = m.group(1) st = '' try: st = str(eval(code, scope)) except SyntaxError: exec code in scope return st# 解析: code='7+9'# str(eval(code, scope))='16'print re.sub('\[(.+?)\]', replacement, example_string_1)# output> the sum of 7 and 9 is 16.# 两次替换# 解析1: code="name = 'Mr.Gumby'"# eval(code)# raise SyntaxError# exec code in scope# 在命名空间 scope 中将 "Mr.Gumby" 赋给了变量 name# 解析2: code="name"# eval(name) 返回变量 name 的值 Mr.Gumbyprint re.sub('\[(.+?)\]', replacement, example_string_2)# output> Hello,Mr.Gumby
    • subn(pattern, repl, string, count=0, flags=0)

作用与函数 sub 一样, 唯一不同之处在于返回值为一个元组,第一个值为替换后的字符串,第二个值为发生替换的次数

    • template(pattern, flags=0)

这个吧,咋一看和 compile 差不多,不过不支持 +、?、*、{} 等这样的元字符,只要是需要有重复功能的元字符,就不支持,查了查资料,貌似没人知道这个函数到底是干嘛的...

四、re 内置对象用法

    • SRE_Pattern 这个对象是一个编译后的正则表达式,编译后不仅能够复用和提升效率,同时也能够获得一些其他的关于正则表达式的信息

属性:

  • flags 编译时指定的模式

  • groupindex 以正则表达式中有别名的组的别名为键、以该组对应的编号为值的字典,没有别名的组不包含在内。

  • groups 正则表达式中分组的数量

  • pattern 编译时用的正则表达式

  • s = 'Hello, Mr.Gumby : 2016/10/26'p = re.compile('''(?: # 构造一个不捕获分组 用于使用 |(?P\w+\.\w+) # 匹配 Mr.Gumby| # 或(?P\s+\.\w+) # 一个匹配不到的命名分组).*? # 匹配 :(\d+) # 匹配 2016''', re.X)#print p.flags# output> 64print p.groupindex# output> {'name': 1, 'no': 2}print p.groups# output> 3print p.pattern# output> (?: # 构造一个不捕获分组 用于使用 |# (?P\w+\.\w+) # 匹配 Mr.Gumby# | # 或# (?P\s+\.\w+) # 一个匹配不到的命名分组# )# .*? # 匹配 :# (\d+) # 匹配 2016

函数:可使用 findall、finditer、match、search、split、sub、subn 等函数

    • SRE_Match 这个对象会保存本次匹配的结果,包含很多关于匹配过程以及匹配结果的信息

属性:

  • endpos 本次搜索结束位置索引

  • lastgroup 本次搜索匹配到的最后一个分组的别名

  • lastindex 本次搜索匹配到的最后一个分组的索引

  • pos 本次搜索开始位置索引

  • re 本次搜索使用的 SRE_Pattern 对象

  • regs 列表,元素为元组,包含本次搜索匹配到的所有分组的起止位置

  • string 本次搜索操作的字符串

s = 'Hello, Mr.Gumby : 2016/10/26'm = re.search(', (?P\w+\.\w+).*?(\d+)', s)# 本次搜索的结束位置索引print m.endpos# output> 28# 本次搜索匹配到的最后一个分组的别名# 本次匹配最后一个分组没有别名print m.lastgroup# output> None# 本次搜索匹配到的最后一个分组的索引print m.lastindex# output> 2# 本次搜索开始位置索引print m.pos# output> 0# 本次搜索使用的 SRE_Pattern 对象print m.re# output> <_sre.SRE_Pattern object at 0x000000000277E158>

函数:

  • end([group=0]) 返回指定分组的结束位置,默认返回正则表达式所匹配到的最后一个字符的索引

  • expand(template) 根据模版返回相应的字符串,类似与 sub 函数里面的 repl, 可使用 \1 或者 \g 来选择分组

  • group([group1, ...]) 根据提供的索引或名字返回响应分组的内容,默认返回 start() 到 end() 之间的字符串, 提供多个参数将返回一个元组

  • groupdict([default=None]) 返回 返回一个包含所有匹配到的命名分组的字典,没有命名的分组不包含在内,key 为组名, value 为匹配到的内容,参数 default 为没有参与本次匹配的命名分组提供默认值

  • groups([default=None]) 以元组形式返回每一个分组匹配到的字符串,包括没有参与匹配的分组,其值为 default

  • span([group]) 返回指定分组的起止位置组成的元组,默认返回由 start() 和 end() 组成的元组

  • start([group]) 返回指定分组的开始位置,默认返回正则表达式所匹配到的第一个字符的索引

s = 'Hello, Mr.Gumby : 2016/10/26'm = re.search('''(?: # 构造一个不捕获分组 用于使用 |(?P\w+\.\w+) # 匹配 Mr.Gumby| # 或(?P\s+\.\w+) # 一个匹配不到的命名分组).*? # 匹配 :(\d+) # 匹配 2016''',s, re.X)# 返回指定分组的结束位置,默认返回正则表达式所匹配到的最后一个字符的索引print m.end()# output> 22# 根据模版返回相应的字符串,类似与 sub 函数里面的 repl, 可使用 \1 或者 \g 来选择分组print m.expand("my name is \\1")# output> my name is Mr.Gumby# 根据提供的索引或名字返回响应分组的内容,默认返回 start() 到 end() 之间的字符串, 提供多个参数将返回一个元组print m.group()# output> Mr.Gumby : 2016print m.group(1,2)# output> ('Mr.Gumby', None)# 返回 返回一个包含所有匹配到的命名分组的字典,没有命名的分组不包含在内,key 为组名, value 为匹配到的内容,参数 default 为没有参与本次匹配的命名分组提供默认值print m.groupdict('default_string')# output> {'name': 'Mr.Gumby', 'no': 'default_string'}# 以元组形式返回每一个分组匹配到的字符串,包括没有参与匹配的分组,其值为 defaultprint m.groups('default_string')# output> ('Mr.Gumby', 'default_string', '2016')# 返回指定分组的起止未知组成的元组,默认返回由 start() 和 end() 组成的元组print m.span(3)# output> (18, 22)# 返回指定分组的开始位置,默认返回正则表达式所匹配到的第一个字符的索引print m.start(3)# output> 18

五、分组用法

python 的正则表达式中用小括号 "(" 表示分组,按照每个分组中前半部分出现的顺序 "(" 判定分组的索引,索引从 1 开始,每个分组在访问的时候可以使用索引,也可以使用别名

s = 'Hello, Mr.Gumby : 2016/10/26'p = re.compile("(?P\w+\.\w+).*?(\d+)(?#comment)")m = p.search(s)# 使用别名访问print m.group('name')# output> Mr.Gumby# 使用分组访问print m.group(2)# output> 2016

有时候可能只是为了把正则表达式分组,而不需要捕获其中的内容,这时候可以使用非捕获分组

s = 'Hello, Mr.Gumby : 2016/10/26'p = re.compile("""(?: # 非捕获分组标志 用于使用 |(?P\w+\.\w+)|(\d+/))""", re.X)m = p.search(s)# 使用非捕获分组# 此分组将不计入 SRE_Pattern 的 分组计数print p.groups# output> 2# 不计入 SRE_Match 的分组print m.groups()# output> ('Mr.Gumby', None)

如果你在写正则的时候需要在正则里面重复书写某个表达式,那么你可以使用正则的引用分组功能,需要注意的是引用的不是前面分组的 正则表达式 而是捕获到的 内容,并且引用的分组不算在分组总数中.

s = 'Hello, Mr.Gumby : 2016/2016/26'p = re.compile("""(?: # 非捕获分组标志 用于使用 |(?P\w+\.\w+)|(\d+/)).*?(?P\d+)/(?P=number)/""", re.X)m = p.search(s)# 使用引用分组# 此分组将不计入 SRE_Pattern 的 分组计数print p.groups# output> 3# 不计入 SRE_Match 的分组print m.groups()# output> ('Mr.Gumby', None, '2016')# 查看匹配到的字符串print m.group()# output> Mr.Gumby

今天的知识点就分享给大家到这里,祝大家早日成为Python大牛,早点买到年后高峰火车票返回目的地,想要获取更多学习资源和教程,请私聊爱编程的南风头条号,私信关键词:学习资料。

®淘文网™ | 版权所有
㊣ 转载请附上文章链接并注明: 淘文网 » 春运返程高峰,用Python分析12306抢票、自动刷票的案例
㊣ 本文永久链接: http://www.taosum.com/yule/24968.html