分享

多线程爬取电影天堂最新电影

 yespon 2017-11-19

life is short,use python!

背景

最近刚学了python想练练手,刚好周末到电影天堂上找电影看,发现网页的广告很烦,就想着写个爬虫定期去撸一遍过滤有效信息存到本地数据库,随时查看岂不是美滋滋,抄起python就是干!

1. 抓取电影列表页

先定为到列表页http:///html/gndy/dyzz/list_23_1.html,这个网址是最新电影的第一页,替换网址最后面的1为2、3、4…直到166就得到了电影列表的所有页面,默认是数字越大电影上映时间越久。

使用pythonrequests库爬取166个电影列表页,提取到所有电影详情页地址:

# 请求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.',            '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./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.'    for seg in segments:        result = result + host + seg + '\n'    return result

执行fetch_movie_urls('movie_urls.txt')得到一个movie_urls.txt文本文件,里面包含所有电影详情的url地址以\n分割。

2. 抓取详情页提取有效信息

访问上一步骤文本中具体的一个电影详情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

3. 多线程执行爬取操作

上面第一个步骤已经得到所有电影的详情地址,第二个步骤提取到具体某一个电影的详细信息,如果使用单线程一个个的去取也能得到全部信息,但是网络操作大部分时间都是在等待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))

4. 导入数据库

上面几个步骤得到了一个包含所有电影信息的有固定格式的文本文:一条电影占一行,不同字段以;分割,这样我们可以使用mysql数据库的load data infile指令轻松的将文本导入数据库中。在python3中使用MySQLdb模块连接本地数据库。

5. 成果


源码地址:https://github.com/lonnyzhang423/dytt.git

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多