life is short,use python!
最近刚学了
想练练手,刚好周末到电影天堂上找电影看,发现网页的广告很烦,就想着写个爬虫定期去撸一遍过滤有效信息存到本地数据库,随时查看岂不是美滋滋,抄起python就是干!
- python
先定为到列表页http://dytt8.net/html/gndy/dyzz/list_23_1.html,这个网址是最新电影的第一页,替换网址最后面的1为2、3、4…直到166就得到了电影列表的所有页面,默认是数字越大电影上映时间越久。
使用
的
- python
库爬取166个电影列表页,提取到所有电影详情页地址:
- requests
- # 请求url页面def do_request(url): try: headers = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.8', 'Host': 'www.ygdy8.net', 'Connection': 'keep-alive', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)' } resp = requests.get(url, headers=headers, timeout=10) resp.encoding = 'gb18030' return resp.text except requests.exceptions.RequestException as e: return ''# 提取影片urldef fetch_movie_urls(filename): if not os.path.isfile(filename): with open(filename, 'w') as file_url: base_url = 'http://www.ygdy8.net/html/gndy/dyzz/list_23_{0}.html' for page in range(1, 166): html = do_request(base_url.format(str(page))) urls = extract_urls(html) file_url.write(urls)
代码中的
方法是用正则表达式匹配一个列表页中所有的电影详情地址,函数返回的是以
- extract_urls(html)
分割的电影列表字符串:
- \n
- import re# 正则表达式r_url = re.compile('.*', re.DOTALL)# 提取电影urldef extract_urls(html): result = '' segments = r_url.findall(html) host = 'http://www.ygdy8.net' for seg in segments: result = result + host + seg + '\n' return result
执行
得到一个
- fetch_movie_urls('movie_urls.txt')
文本文件,里面包含所有电影详情的url地址以
- movie_urls.txt
分割。
- \n
访问上一步骤文本中具体的一个电影详情url就进入到电影详情页面,在这个页面我们可以提取到电影的具体信息,包括电影名称、语言、国家、上映时间、简介、演员、下载地址等等。
提取这些信息的正则表达式如下:
- import re# 匹配影片详细信息r_name_cn = re.compile(u'◎译名(.*?)
- ', re.DOTALL)r_name = re.compile(u'◎片名(.*?)
- ', re.DOTALL)r_year = re.compile(u'◎年代(.*?)
- ', re.DOTALL)r_country = re.compile(u'◎(产地|国家)(.*?)
- ', re.DOTALL)r_category = re.compile(u'◎类别(.*?)
- ', re.DOTALL)r_language = re.compile(u'◎语言(.*?)
- ', re.DOTALL)r_subtitle = re.compile(u'◎字幕(.*?)
- ', re.DOTALL)r_release_date = re.compile(u'◎上映日期(.*?)
- ', re.DOTALL)r_score = re.compile(u'◎(IMDB评分|豆瓣评分)(.*?)
- ', re.DOTALL | re.IGNORECASE)r_file_size = re.compile(u'◎文件大小(.*?)
- ', re.DOTALL)r_movie_duration = re.compile(u'◎片长(.*?)
- ', re.DOTALL)r_director = re.compile(u'◎导演(.*?)
- ', re.DOTALL)r_download_url = re.compile('.*(.*?)', re.DOTALL)r_list = (r_name_cn, r_name, r_year, r_country, r_category, r_language, r_subtitle, r_release_date, r_score, r_file_size, r_movie_duration, r_director) # 提取电影详情def extract_details(html): details = '' if html: fields = [] for regex in r_list: m = regex.search(html) if not m: field = '' else: field = m.group(m.lastindex).replace(' ', '').replace(';', ',').strip() fields.append(field) urls = r_download_url.findall(html) field = '' if urls: for url in urls: field = field + url.strip() if urls.index(url) != len(urls) - 1: field = field + ',' fields.append(field) details = ''.join(map(lambda x: ''' + x + '';', fields)) + '\n' return details
上面第一个步骤已经得到所有电影的详情地址,第二个步骤提取到具体某一个电影的详细信息,如果使用单线程一个个的去取也能得到全部信息,但是网络操作大部分时间都是在等待
,太浪费时间了,使用多线程可以大大缩短代码执行的时间。
- IO
多线程爬取得到的结果先写到文件中,为了保证写操作同步,考虑使用两个队列,第一个输入队列用来存储第一步得到的所有电影详情的url,第二个输出队列用来存储第二个步骤得到的电影详情信息,然后启动单独的写线程不断的从输出队列读取信息向同一文件中写入电影详情信息,直到写线程从输出队列读取到退出元素为止。
- # 工作线程class Spider(threading.Thread): ''' 1.从输入队列中提取电影详情url请求并提取有效信息 2.将有效信息放入输出队列中供输出线程记录 ''' def __init__(self, in_queue, out_queue): super(Spider, self).__init__() self.in_queue = in_queue self.out_queue = out_queue def run(self): while True: if self.in_queue.empty(): logger.debug('in_queue empty.{0} exiting...'.format(threading.current_thread().getName())) break else: url = self.in_queue.get() if url: result = Spider.process(url) self.out_queue.put(result) @staticmethod def process(url): html = do_request(url.strip()) return extract_details(html)
- # 记录线程class Writer(threading.Thread): ''' 输出线程:不断从队列中取出处理完的信息并记录到文件 直到取到的元素为退出标识为止 ''' __exit = True def __init__(self, queue, filename): super(Writer, self).__init__() self.queue = queue self.filename = filename def stop(self): self.queue.put(self.__exit) def run(self): with open(self.filename, 'wb') as f: while True: data = self.queue.get() if not data: continue if data is self.__exit: break f.write(data.encode('utf8')) logger.debug('write file:{0}'.format(data))
执行:
- def main(): # 电影地址文件 path_urls = os.path.join(directory, 'movie_urls.txt') # 电影详情文件 path_movies = os.path.join(directory, 'movies.txt') start = time.time() in_queue = Queue() out_queue = Queue() with open(path_urls, 'r') as f: lines = f.readlines() for line in lines: in_queue.put(line) # 启动记录线程 writer = Writer(out_queue, filename=path_movies) writer.start() # 启动爬虫线程 spiders = [Spider(in_queue, out_queue) for i in range(20)] for s in spiders: s.start() for s in spiders: s.join() # 爬虫线程结束,向输出线程发出结束信号 writer.stop() writer.join() logger.debug('all done({0}s).'.format(time.time() - start))
上面几个步骤得到了一个包含所有电影信息的有固定格式的文本文:一条电影占一行,不同字段以
分割,这样我们可以使用
- ;
数据库的
- mysql
指令轻松的将文本导入数据库中。在
- load data infile
中使用
- python3
模块连接本地数据库。
- MySQLdb
源码地址:https://github.com/lonnyzhang423/dytt.git
百度搜索“就爱阅读”,专业资料,生活学习,尽在就爱阅读网92to.com,您的在线图书馆!
来源: http://www.92to.com/bangong/2017/11-19/31399304.html