├── README.MD └── get_iptv_channels.py /README.MD: -------------------------------------------------------------------------------- 1 | 电信IPTV组拔及回放地址获取 2 | === 3 | # 项目功能 4 | - 基于python3的数据抓取 5 | - 通过IPTV线路获取电信IPTV组拔地址以及回放的地址 6 | - 生成txt及m3u文件 7 | # 使用范围 8 | - 成都电信IPTV 9 | - 其他地区及其他运营商估计大同小异(未测试) 10 | # 使用前提 11 | - 必须获取到IPTV接口的IP地址 12 | - 必须获取到秘钥key 13 | - 必须获取到UserID mac STBID ip STBType STBVersion UserAgent信息 14 | ```python 15 | key = '' 16 | UserID = '' 17 | mac = '' 18 | STBID = '' 19 | ip = '' 20 | STBType = '' 21 | STBVersion = '' 22 | UserAgent = '' 23 | ``` 24 | # 使用方法 25 | ``` 26 | python3 get_iptv_channels 27 | ``` 28 | # 其他问题 29 | ## 参数获取 30 | UserID mac STBID ip STBType STBVersion UserAgent信息可由抓包以及查看机顶盒背面获得 31 | ## 秘钥获取 32 | 抓包获取到Authenticator后,配置Authenticator的值 33 | ```python 34 | Authenticator = 'XXX' 35 | ``` 36 | 通过本程序中的find_key()方法测试获得。 37 | 38 | #### 另:业余选手,请多关照 39 | -------------------------------------------------------------------------------- /get_iptv_channels.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | ''' 3 | name:成都电信IPTV电视频道组播及回放列表获取 4 | author:业余选手-老张 5 | 仅供测试研究使用! 6 | 7 | 使用前提: 8 | 1、必须获取到IPTV接口的IP地址 9 | 2、必须获取到秘钥 10 | 3、必须抓包获取到UserID mac STBID ip STBType STBVersion UserAgent信息 11 | 使用方法 12 | 1、在上述前提条件下直接运行get_channel() 13 | 2、如果没有加密Authenticator的key,可以抓包获取Authenticator,然后使用find_key()方法获取到key 14 | 获取流程 15 | 获取token--获取session--请求频道列表--处理后保存 16 | 17 | 此方法只在成都电信测试通过,其他其他地区电信应该大同小异, 18 | 可能只需要更改 “182.138.3.142:8082”为当地的地址(抓包获取到的第一条HTTP请求地址)即可。---未测试 19 | 其他运营商未测试,请自行测试。 20 | ''' 21 | 22 | import requests,re,time,random,os 23 | from Crypto.Cipher import DES3 24 | from urllib.parse import urlparse 25 | #################################下列信息需要自行补充##################################################### 26 | key = '' #8位数字,加密Authenticator的秘钥,每个机顶盒可能都不同,获取频道列表必须使用 27 | #下面的信息全部都可以由抓包获取到USERID,mac,stbid(mac,stbid机顶盒背面查询),ip(当前IPTV网络的IP地址), 28 | UserID = '' 29 | mac = '' 30 | STBID = '' 31 | ip = '10.10.10.10' #随便一个IPTV地址即可,不检测 32 | STBType = '' 33 | STBVersion = '' 34 | UserAgent = '' 35 | #如要获取Authenticator加密的秘钥,请填写Authenticator,并使用find_key(Authenticator)测试key值 36 | Authenticator = '' 37 | ###################################################################################################### 38 | save_dir_txt = os.getcwd()+'/sctv.txt' #频道信息保存目录 39 | save_dir_m3u = os.getcwd()+'/sctv.m3u' #生成m3u文件 40 | 41 | date_now = time.strftime('%Y-%m-%d %X',time.localtime()) 42 | BS = DES3.block_size 43 | def pad(s): 44 | p = s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 45 | return p 46 | def unpad(s): 47 | p = s[0:-ord(s[-1])] 48 | return p 49 | class prpcrypt(): 50 | def __init__(self,key): 51 | self.key = key + '0'*16 52 | self.mode = DES3.MODE_ECB 53 | def encrypt(self, text): #加密文本字符串,返回 HEX文本 54 | text = pad(text) 55 | cryptor = DES3.new(self.key, self.mode) 56 | x = len(text) % 8 57 | if x != 0: 58 | text = text + '\0' * (8 - x) 59 | self.ciphertext = cryptor.encrypt(text) 60 | return self.ciphertext.hex() 61 | def decrypt(self, text):#需要解密的字符串,字符串为十六进制的字符串 如"a34f3e3583".... 62 | try: 63 | cryptor = DES3.new(self.key, self.mode) 64 | except Exception as e: 65 | if 'degenerates' in str(e): 66 | raisetxt = 'if key_out[:8] == key_out[8:16] or key_out[-16:-8] == key_out[-8:]:\nraise ValueError("Triple DES key degenerates to single DES")' 67 | print('请将调用的DES3.py文件里adjust_key_parity方法中的:%s 注释掉'%raisetxt) 68 | else: 69 | print(e) 70 | de_text = bytes.fromhex(text) 71 | plain_text = cryptor.decrypt(de_text) 72 | return plain_text.replace(b'\x08',b'').decode('utf-8') #返回 string,不需要再做处理 73 | #获取token,通过此token来获取session 74 | def get_token(): 75 | url = 'http://182.138.3.142:8082/EDS/jsp/AuthenticationURL?UserID=%s&Action=Login'%(UserID) 76 | headers = { 77 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 78 | 'User-Agent': UserAgent, 79 | 'X-Requested-With': 'com.android.smart.terminal.iptv', 80 | } 81 | res = requests.get(url,headers = headers,timeout = 10) 82 | host = urlparse(res.url).netloc 83 | 84 | url = 'http://%s/EPG/jsp/authLoginHWCTC.jsp'%host 85 | headers = { 86 | 'User-Agent': UserAgent, 87 | 'Content-Type': 'application/x-www-form-urlencoded', 88 | 'Referer': 'http://%s/EPG/jsp/AuthenticationURL?UserID=%s&Action=Login'%(host,UserID), 89 | 'X-Requested-With': 'com.android.smart.terminal.iptv', 90 | } 91 | data = { 92 | 'UserID':UserID, 93 | 'VIP':'' 94 | } 95 | res = requests.post(url,headers = headers,data = data,timeout = 10) 96 | res.encoding = 'utf-8' 97 | txt = res.text 98 | r_enc = re.search('EncryptToken \= \"(.+?)\";.+?userToken\.value \= \"(.+?)\"',txt,re.DOTALL) 99 | EncryptToken = r_enc.group(1) 100 | userToken = r_enc.group(2) 101 | ret = { 102 | 'host':host, 103 | 'token':EncryptToken, 104 | } 105 | ret = [host,EncryptToken] 106 | return ret 107 | #测试秘钥,如果已经抓包获取到了Authoritor,可以使用此方法获取秘钥,可能会获取到很多个,任何一个均可正常使用 108 | def find_key(Authenticator): 109 | keys = [] 110 | while len(Authenticator) < 10: 111 | Authenticator = input('未配置Authenticator,请输入正确的Authenticator的值:') 112 | print('开始测试00000000-99999999所有八位数字') 113 | for x in range(100000000): 114 | key = str('%08d'%x) 115 | if x % 500000 == 0: 116 | print('已经搜索至:-- %s -- '%key) 117 | pc = prpcrypt('%s'%key) 118 | try: 119 | ee = pc.decrypt(Authenticator) 120 | infos = ee.split('$') 121 | infotxt = ' 随机数:%s\n TOKEN:%s\n USERID:%s\n STBID:%s\n ip:%s\n mac:%s\n 运营商:%s'%(infos[0],infos[1],infos[2],infos[3],infos[4],infos[5],infos[7]) if len(infos)>7 else '' 122 | printtxt = '找到key:%s,解密后为:%s\n%s'%(x,ee,infotxt) 123 | print(printtxt) 124 | keys.append(key) 125 | except Exception as e: 126 | pass 127 | 128 | with open(os.getcwd() +'/key.txt','w') as f: 129 | line = '%s\n共找到KEY:%s个,分别为:%s\n解密信息为:%s\n详情:%s'%(date_now,len(keys),','.join(keys),str(ee),infotxt) 130 | f.write(line) 131 | f.flush() 132 | print('解密完成!共查找到 %s 个密钥,分别为:%s'%(len(keys),keys))# 133 | #获取IPTV的session,后面的请求全部需要提交此session 134 | def getSession(key): 135 | n = 0 136 | while n < 5: #重试 137 | try: 138 | host,token = get_token() 139 | url = 'http://%s/EPG/jsp/ValidAuthenticationHWCTC.jsp'%host 140 | rand = ''.join(random.sample('123456789',8)) 141 | session_ref = '%s$%s$%s$%s$%s$%s$$CTC'%(rand,token,UserID,STBID,ip,mac) # 随机8位数 +$+TOKEN +$+USERID +$+STBID +$ip +$+mac +$$CTC 142 | Authenticator = prpcrypt(key).encrypt(session_ref) 143 | headers = { 144 | 'User-Agent': UserAgent, 145 | 'Content-Type': 'application/x-www-form-urlencoded', 146 | 'Referer': 'http://%s/EPG/jsp/authLoginHWCTC.jsp'%host, 147 | } 148 | data = { 149 | 'UserID': UserID, 150 | 'Lang': '', 151 | 'SupportHD': '1', 152 | 'NetUserID': '', 153 | 'Authenticator': Authenticator, 154 | 'STBType': STBType, 155 | 'STBVersion': STBVersion, 156 | 'conntype': '', 157 | 'STBID': STBID, 158 | 'templateName': '', 159 | 'areaId': '', 160 | 'userToken': token, 161 | 'userGroupId': '', 162 | 'productPackageId': '', 163 | 'mac': mac, 164 | 'UserField': '', 165 | 'SoftwareVersion': '', 166 | 'IsSmartStb': 'undefined', 167 | 'desktopId': 'undefined', 168 | 'stbmaker': '', 169 | 'VIP': '', 170 | } 171 | res = requests.post(url,headers = headers,data = data,timeout = 10) 172 | res.encoding = 'utf-8' 173 | re_token = re.search('UserToken\" value\=\"(.+?)\".+?stbid\" value\=\"(.+?)\"',res.text,re.DOTALL) 174 | user_token = re_token.group(1) 175 | stbid_ = re_token.group(2) 176 | ret = [host,res.cookies,user_token,stbid_] 177 | return ret 178 | except Exception as e: 179 | n += 1 180 | time.sleep(3) 181 | print(e) 182 | def get_channel_list(host,usertoken,cookies,stbid): 183 | url = 'http://%s/EPG/jsp/getchannellistHWCTC.jsp' % host 184 | data = { 185 | 'conntype':'', 186 | 'UserToken':usertoken, 187 | 'tempKey':'', 188 | 'stbid':stbid, 189 | 'SupportHD':'1', 190 | 'UserID':UserID, 191 | 'Lang':'1' 192 | } 193 | n = 1 #重试次数 194 | while n < 5: 195 | try: 196 | res = requests.post(url,data = data,cookies = cookies) 197 | break 198 | except Exception as e: 199 | print('获取成都电信频道列表 失败:%s'%e) 200 | n += 1 201 | time.sleep(3) 202 | res.encoding = 'utf-8' 203 | all_channels = re.findall('ChannelID\=\"(\d+)\",ChannelName\=\"(.+?)\",UserChannelID\=\"\d+\",ChannelURL=\"igmp://(.+?)\".+?TimeShift\=\"(\d+)\",TimeShiftLength\=\"(\d+)\".+?,TimeShiftURL\=\"(.+?)\"',res.text)# 204 | channels = [] 205 | for channel in all_channels: 206 | channel = list(channel) 207 | url_re = re.match('(.+?\.smil)?', channel[5]) 208 | channel[5] = url_re.group(1) 209 | channels.append(channel) 210 | print('共获取频道数量为:%s,文件存储于目录的:%s及%s.'%(len(channels),save_dir_txt,save_dir_m3u)) 211 | return channels 212 | def get_channels(key): 213 | print('%s 开始运行'%date_now) 214 | print('仅供测试使用,用于成都电信IPTV,其他地区请自行更改get_token中的url') 215 | while len(key) != 8: 216 | key = input('请输入8位数的key:') 217 | try: 218 | host, cookies, usertoken, stbid = getSession(key) 219 | if len(cookies['JSESSIONID']) < 5: 220 | print('未获取到SESSION,请检查:key、MAC、stbid、UserID等已正确配置!') 221 | return 222 | except Exception as e: 223 | print('获取SESSION失败,请检查网络!:%s'%e) 224 | return 225 | print('已经获取到session:\nusertoken:%s,\nJSESSIONID:%s'%(usertoken,cookies['JSESSIONID'])) 226 | channels = get_channel_list(host,usertoken,cookies,stbid) 227 | ftxt = open(save_dir_txt,'w') 228 | fm3u = open(save_dir_m3u,'w') 229 | m3uline1 = '#EXTM3U\n' 230 | fm3u.write(m3uline1) 231 | ftxt.write(date_now) 232 | ftxt.write('\n组播地址列表--by:老张\n本次共共获取频道数量:%s\n可直接将以下信息复制到EXCEL\n'%(len(channels))) 233 | ftxt.write('%s\t%s\t%s\t%s\n'%('频道ID','频道名称','组拔地址','回放地址')) 234 | for channel in channels: 235 | m3uline = '#EXTINF:-1 ,%s\nrtp://%s\n'%(channel[1],channel[2]) 236 | txtline = '%s\t%s\t%s\t%s\n'%(channel[0],channel[1],channel[2],channel[5]) 237 | ftxt.write(txtline) 238 | fm3u.write(m3uline) 239 | ftxt.close() 240 | fm3u.close() 241 | get_channels(key) #获取组播频道列表 242 | #find_key(Authenticator) #测试KEY 243 | --------------------------------------------------------------------------------