python爬虫之双色球所有历史中奖信息
这篇Blog主要介绍爬取 www.500.com 网站中所有双色球的历史开奖即中奖情况信息
首先分析网页的整体分布,和定制好需要爬取的信息。重中之重,一定明确爬取信息需求,这个不仅关系到后面的程序设计,还有可能因为一些并不需要的信息加大了爬取难度及持久化操作(我就因为一些不必要需要在信息刷选爬取过程中遇到大问题)。
这个页面包含了我们所需有爬取的信息,在这里我准备爬取的信息有期数、开奖日期、开奖号码、本期销量、奖池滚动和中奖注数。起初我还想按奖项把内容爬取下来,然后用字典把每个奖项的中奖注数和单注奖金存储,后面刚开始没有进行持久化操作,运行程序就爆炸了,想想2000多个网页,内存肯定爆炸,我起初没注意是这个问题造成内存泄露问题,还一直以为是循环使用对象了,还以看了许久python的垃圾回收。总之这个问题导致我程序爬取到400多个请求的时候导致进程被系统杀死,吃一堑长一智啊!避免无限向系统申请内存,超大列表或字典会把程序搞死的。另外后面设计数据库以及分析爬取数据都是很麻烦的事,所以定制好可行科学的需求。
好了,言归正传,既然明白了自己的需求下面开始分析,查看上角下拉框,点开几期查看,你会发现网站数据==不是动态生成的==!!!那你的分析工作就会轻松很多,因为这个相当于爬取静态网站的内容!
通过观察每个网页你会发现它的Url组成是有规律的:每个网页只有数字不一样,正好这些数字就是你要查询的期数,然后很容易就想到用循环创建这些Url,然后用数列存储起来,后面调用就行,但是这样考虑问题就会导致后面get请求时,老是到03089期后面报错,其实这个并不是请求问题,而是根本就没有03090以及03开头后面的内容,这五位数的含义是,前两个代表年份,后面三个代表期数,所以用上面的方法来获取肯定会报错。后来自己查看网页源码,发现竟然有所有期数,所以可以直接爬取,不要去重后处理数据了,直接爬取,join就行,代码如下:
def GetUrls(self): """收集所有子页面的url""" baseUrlHead = "http://kaijiang.500.com/shtml/ssq/" baseUrlEnd = ".shtml" html = 'http://kaijiang.500.com/ssq.shtml' htmlSource = requests.get(html).content.decode('gbk') Selector=etree.HTML(htmlSource) UrlMid = Selector.xpath('//*[@class="iSelectList"]/a/text()') UrlMid.reverse for baseUrlMid in UrlMid: #以下两种方式都能链接,但是在数量变大的时候,join方法效率更高,推荐使用,join方法操作的是可迭代对象!! url = ''.join([baseUrlHead,baseUrlMid,baseUrlEnd]) # url = baseUrlHead + baseUrlMid +baseUrlEnd self.Urls.append(url)
这里我采用xpath分析网页,其实还可以用Beautiful4库进行分析,但是xpath效率还有使用起来会更高效,推荐使用!
信息爬取
已经获取所有网页的url,下面进行信息筛选和爬取,这个这边就不赘述了,就是基本操作,代码如下:
def GetInfo(self htmlSource): """爬取信息""" Selector = etree.HTML(htmlSource)#转换为xpath能查询的文本 #获取期数、 term = Selector.xpath('//td[@class="td_title01"]/span/a/font/strong/text()')[0] #获取开奖日期 date = str(Selector.xpath('//td[@class="td_title01"]/span/text()')[1]) date = date.split(' ')[1].split(':')[1] #获取开奖日期,注意其中:是中文符号的 #获取开奖号码 num = Selector.xpath('//div[@class="ball_box01"]/ul/li/text()') num = " ".join(num) #连接获取中奖号码 #获取当前销量和滚动奖池 money = Selector.xpath('//table[@class="kj_tablelist02"]/tr/td/span/text()') #销售量为下标为2, 奖池下标为3 saleMoney = str(''.join(money[2].split(',')).partition('元')[0]) jackpot = str(''.join(money[3].split(',')).partition('元')[0]) #在并发处理的时候,可能会会出现下标越界问题,不知道是不是因为requests线程不安全还是啥的,数据会丢失,所以得重新收集销量信息 Prize = [] for i in range(3,9): prize = Selector.xpath('//table[@class="kj_tablelist02"]/tr[%d]/td/text()'%i) if i == 3: try: #格式化数据 num_ = prize[6].replace('\r\n\t\t\t\t','') prize[7].replace('\r\n\t\t\t\t','') except: num_ = prize[5].replace('\r\n\t\t\t\t','') if num_.isdigit(): pass else: num_ = 0 else: num_ = prize[1].replace('\r\n\t\t\t\t','') Prize.append(num_) #列表用以后面数据持久化操作时循环取出
最后进行在测试的时候会发现老是发生ConnectError异常,这是因为单个IP频繁快速网页时会对服务器造成负担,所以服务器会拒绝该ip的访问。为了解决这个问题,我开始是设定了阿里云的DNS,以为解析速度加快肯定会有缓解connecterror,但治标不治本,最后添加了代理池,随机ip访问,终于根治这个问题。
proxy = [ {'https':'https://183.30.204.252:9000'}, {'https':'https://183.30.204.252:9999'}, {'https':'https://222.186.15.232:63229'}, {'https':'https://119.27.177.169:80'}, {'https':'https://183.129.207.73:14823'}, {'https':'https://221.217.49.196:9000'}, ]
这个代理IP可以上西刺免费代理IP 获取,怎么使用进程池网上也有很多教程,如果有问题可以进行交流。
还有就是插入数据库了,数据库设计很简单,就是按照上面获取的数据进行设计就好,其中因为可能出现不中奖出现 – ,所以设定中奖注数全为string类型了,当然可以选择添加筛选功能,把没有中奖注数的改为0就行。
sql_insert = "INSERT INTO lottery(term,date,num,saleMoney,jackpot,prize1,prize2,prize3,prize4,prize5,prize6)VALUES('%d','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')"%(int(term),str(date),num,saleMoney,jackpot,Prize[0],Prize[1],Prize[2],Prize[3],Prize[4],Prize[5]) Db.insert(sql_insert) #其中Db模块是我自己写的对mysql数据库操作的库
下面就整合以一下代码
from lxml import etree import requests import time import random import threading import Db from multiprocessing import Process,Pool class Spider(object): """爬取彩票的历史开奖结果""" def __init__(self): self.Urls = [] #收集的地址 self.Htmls = [] #收集html文本 def GetUrls(self): """收集所有子页面的url""" baseUrlHead = "http://kaijiang.500.com/shtml/ssq/" baseUrlEnd = ".shtml" html = 'http://kaijiang.500.com/ssq.shtml' htmlSource = requests.get(html).content.decode('gbk') Selector=etree.HTML(htmlSource) UrlMid = Selector.xpath('//*[@class="iSelectList"]/a/text()') UrlMid.reverse for baseUrlMid in UrlMid: #以下两种方式都能链接,但是在数量变大的时候,join方法效率更高,推荐使用,join方法操作的是可迭代对象!! url = ''.join([baseUrlHead,baseUrlMid,baseUrlEnd]) # url = baseUrlHead + baseUrlMid +baseUrlEnd self.Urls.append(url) def GetHtml(self, index, proxy_): """获取页面html""" #可以直接使用for循环网址,这里选择使用这个主要enumerate有lazy性,只有当是用的时候才会获取这个值,说白了就是生成器。它返回的是索引和值 print(proxy_) flag = len(test.Urls) if index == flag: #判断下标防止越界 start = 2100 end = flag else: start = index - 300 end = index for i,html in enumerate(self.Urls): if start-1 < i < end: # response = None try: print(i) #设置重连次数 requests.adapters.DEFAULT_RETRIES = 5 # s = requests.session() #设置连接状态为false # s.keep_alive = False response = requests.get(html, timeout=(10), proxies=proxy_) htmlSource = response.content.decode('gbk') except requests.exceptions.ConnectionError: print("connection error") except requests.exceptions.Timeout: print('timeouy') continue self.GetInfo(i,htmlSource) def GetInfo(self,htmlSource): """爬取信息""" Selector = etree.HTML(htmlSource)#转换为xpath能查询的文本 #获取期数、 term = Selector.xpath('//td[@class="td_title01"]/span/a/font/strong/text()')[0] #获取开奖日期 date = str(Selector.xpath('//td[@class="td_title01"]/span/text()')[1]) date = date.split(' ')[1].split(':')[1] #获取开奖日期,注意其中:是中文符号的 #获取开奖号码 num = Selector.xpath('//div[@class="ball_box01"]/ul/li/text()') num = " ".join(num) #连接获取中奖号码 #获取当前销量和滚动奖池 money = Selector.xpath('//table[@class="kj_tablelist02"]/tr/td/span/text()') #销售量为下标为2, 奖池下标为3 saleMoney = str(''.join(money[2].split(',')).partition('元')[0]) jackpot = str(''.join(money[3].split(',')).partition('元')[0]) #在并发处理的时候,可能会会出现下标越界问题,不知道是不是因为requests线程不安全还是啥的,数据会丢失,所以得重新收集销量信息 Prize = [] #存取各个类型的中奖注数 for i in range(3,9): prize = Selector.xpath('//table[@class="kj_tablelist02"]/tr[%d]/td/text()'%i) if i == 3: try: num_ = prize[6].replace('\r\n\t\t\t\t','') prize[7].replace('\r\n\t\t\t\t','') except: num_ = prize[5].replace('\r\n\t\t\t\t','') if num_.isdigit(): pass else: num_ = 0 else: num_ = prize[1].replace('\r\n\t\t\t\t','') Prize.append(num_) sql_insert = "INSERT INTO lottery(term,date,num,saleMoney,jackpot,prize1,prize2,prize3,prize4,prize5,prize6)VALUES('%d','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')"%(int(term),str(date),num,saleMoney,jackpot,Prize[0],Prize[1],Prize[2],Prize[3],Prize[4],Prize[5]) Db.insert(sql_insert) if __name__ == '__main__': proxy = [ {'https':'https://183.30.204.252:9000'}, {'https':'https://183.30.204.252:9999'}, {'https':'https://222.186.15.232:63229'}, {'https':'https://119.27.177.169:80'}, {'https':'https://183.129.207.73:14823'}, {'https':'https://221.217.49.196:9000'}, ]test = Spider() s_time = time.time() test.GetUrls() time.sleep(10) for index in (300,600,900,1200,1500,1800,2100,len(test.Urls)): #设定增量 proxy_ = random.choice(proxy) test.GetHtml(index, proxy_) e_time = time.time() print("爬取用时:", e_time - s_time) print(test.count)
虽然能够完美爬取网页上信息,但是效率也忒低了吧,爬取2298个网页内容竟然用了2000多秒,不能忍受!!!
这时候就可以考虑并发爬取了,因为爬取的信息有期数,无需考虑信息是否有序,所以并发爬取都信息处理问题不大,相信细心的 同学已经看到我们上面导入的threading和multiprocessing模快了,下面添加并发编程
if __name__ == '__main__': proxy = [ {'https':'https://183.30.204.252:9000'}, {'https':'https://183.30.204.252:9999'}, {'https':'https://222.186.15.232:63229'}, {'https':'https://119.27.177.169:80'}, {'https':'https://183.129.207.73:14823'}, {'https':'https://221.217.49.196:9000'}, ]test = Spider() s_time = time.time() test.GetUrls() p =Pool() threads = [] i = 0 for index in (300,600,900,1200,1500,1800,2100,len(test.Urls)): #设定增量 print("第%d个进程"%(i+1)) proxy_ = random.choice(proxy) # test.GetHtml(index, proxy_) t = threading.Thread(target=test.GetHtml, args=(index,proxy_)) threads.append(t) # p.apply_async(test.GetHtml,args=(index,proxy_)) # p.close() #关闭进程池 # p.join() for i in range(len(threads)): threads[i].start() for i in range(len(threads)): threads[i].join() e_time = time.time() print("爬取用时:", e_time - s_time) print(test.count)
很简单的就使用了并发,但在使用的时候出现了一点点问题,因为requests线程不安全,有时候会出现数据丢失,在爬取中奖注数时会出现数据丢失,所以在那进行了一波排错小处理。最后我们就成功从单线程向并发进化了。
下面分析一波效率
爬取方式 | 耗时(s) |
---|---|
单线程 | 2260.354 |
多线程 | 158.194 |
多进程 | 153.767 |
这里多进程比多线程慢是开进程耗时所致的吗?后面改成用四个进程和四个线程测试,四进程的会稍微比四线程的快一点,但是相差不大,所以这个程序中随便使用哪种都行。
完整源码已经挂在github上,有需要的同学可以联系我!
阅读更多- Python3爬虫:爬取大众点评网北京所有酒店评分信息
- python数据分析1:获取双色球历史信息
- Python实现人人网爬虫,爬取用户所有状态信息。
- python 爬虫爬取所有上市公司公告信息(一)
- python爬虫抓取豆瓣所有恐怖片信息(利用多线程和构建免费ip代理池)
- python 爬虫爬取所有上市公司公告信息(五)
- python爬虫-模拟登陆新浪微+博爬取感兴趣人的所有信息
- python简单应用!用爬虫来采集天猫所有优惠券信息,写入本地文件
- Python爬虫从入门到放弃(十九)之 Scrapy爬取所有知乎用户信息(下)
- Python实现可获取网易页面所有文本信息的网易网络爬虫功能示例
- python 爬虫爬取所有上市公司公告信息(二)
- Python3爬虫实战:爬取大众点评网某地区所有酒店相关信息
- python 爬虫爬取所有上市公司公告信息(四)
- Java爬虫双色球中奖历史
- Python爬虫从入门到放弃(十九)之 Scrapy爬取所有知乎用户信息(下)
- Python爬虫从入门到放弃(十八)之 Scrapy爬取所有知乎用户信息(上)
- python 爬虫爬取所有上市公司公告信息(三)
- Python爬虫从入门到放弃(十八)之 Scrapy爬取所有知乎用户信息(上)
- Python数据分析之获取双色球历史信息的方法示例
- Python爬虫——使用 lxml 解析器爬取汽车之家二手车信息