赞
踩
今天来说说如何爬取猫眼上的电影信息
最近小编试图使用requests+BeautifulSoup取去抓取猫眼上的电影信息,但尝试一番后,发现输出的电影评分.评分人数和票房都是乱码.案例见下面代码.之后发现无论是使用BeautifulSoup还是正则表达式匹配出来的数据,他都是乱码的,不过使用正则表达式匹配出来的数据他和网页源代码倒是一致的,但我们想要的数字(若有兴趣可以查阅小编的文章,里面有专门的介绍如何破解这种字体方法),在我破解这种字体后,想着大开shajie,尽可能抓取.当我抓取到第32条电影时,突然报错.之后我用浏览器打开网址查看后,原来出现了滑动验证码,之后没办法小编又转用selenium+chrome去抓取,但是使用selenium抓取到的评分数据和下面用BeautifulSoup抓取的数据一样是乱码的,没法用. 之后转用api抓取就能够顺利解决问题了.
-
- import requests
- import re
- from bs4 import BeautifulSoup
- import random
- from fake_useragent import UserAgent
-
- headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
- 'Accept-Encoding': 'gzip, deflate, br',
- 'Accept-Language': 'zh-CN,zh;q=0.9',
- 'Connection': 'keep-alive',
- 'Host': 'maoyan.com',
- 'User-Agent':UserAgent().random,
- 'Referer': 'https://maoyan.com/',
- 'Cookie': '__mta=88838948.1602486634805.1602593489852.1602593548923.36; '
- 'uuid_n_v=v1; uuid=35B885800C5911EBABDB79CD76BA38F3E7502FF14A8E4FA8A0D25E551C5ACA0F;'
- ' _csrf=3412965d4c6fec458c7c07d87c59df17fc573baabc093ba1ac212e5a59a5904c; '
- '_lxsdk_cuid=1751ba58904c8-009e2816945f78-5040231b-144000-1751ba58904b0;'
- ' _lxsdk=35B885800C5911EBABDB79CD76BA38F3E7502FF14A8E4FA8A0D25E551C5ACA0F;'
- ' Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1602486635,1602498569;'
- ' _lx_utm=utm_source%3DBaidu%26utm_medium%3Dorganic; __mta=88838948.1602486634805.1602498583735.1602590114060.26;'
- ' Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2=1602593549; _lxsdk_s=17521d05fba-0a5-ad9-2ce%7C%7C32'
- }
- #使用session发起请求
- session = requests.Session()
- html = session.get('http://maoyan.com/films/246397',headers = headers)
- html.encoding = 'utf-8'
- bs = BeautifulSoup(html.text,'html.parser')
- name = bs.h1
- print(html)
- print('电影名 ',name.get_text())
- #使用BeautifulSoup取获取数据
- score = bs.select('div.movie-stats-container > div:nth-child(1) > div > span > span')[0].get_text()
- people = bs.find('span',{'class':'score-num'})
- print('BeautifulSoup评分: ',score)
- print('BeautifulSoup评分人数 ',people)
- acount = bs.select('div.movie-stats-container > div:nth-child(2) > div > span.stonefont')[0]
- print('BeautifulSoup票房 ',acount)
- #使用正则表达式在html.text中去匹配评分,评分人数以及票房
- people_pattern = re.compile('''<span class='score-num'><span class="stonefont">.*</span>人评分</span>''')
- acount_pattern = re.compile('''<span class="stonefont">.*</span><span class="unit">.*</span>''')
- score_pattern = re.compile('''<span class="index-left info-num ">\n <span class="stonefont">.*</span>''')
- people = people_pattern.findall(html.text)
- score = score_pattern.findall(html.text)
- acount = acount_pattern.findall(html.text)
- print('正则评分: ',score)
- print('正则评分人数 ',people)
- print('正则票房 ',acount)
-
-
-
- #以下是出书结果:
- <Response [200]>
- # 电影名 西游记之大圣归来
- # BeautifulSoup评分: .
- # BeautifulSoup评分人数 <span class="score-num"><span class="stonefont">.万###</span>人评分</span>
- # BeautifulSoup票房 <span class="stonefont">.</span>
- # 正则评分: ['<span class="index-left info-num ">\n <span class="stonefont">.</span>']
- # 正则评分人数 ['<span class=\'score-num\'><span class="stonefont">.万</span>人评分</span>']
- #正则票房 ['<span class="stonefont">.</span><span class="unit">亿</span>']
一 利用API抓取猫眼电影
1.获取猫眼APi接口. 猫眼API接口详见下面URL http://blog.csdn.net/Anana13/article/details/107554897,这里小面只抓取电影信息,故只用了第七个接口电影详情:https://m.maoyan.com/ajax/detailmovie?movieId=1203734
参数:movieId ⇒ 电影ID
2.获取猫眼电影id. 我们知道它的API后,若再知道电影id后面就简单了.现在是获取电影id.在小编抓取到第67也后,下面程序就报错了.之后小编试图手动打开第68页,然而里面却没有任何内容.虽然它显示有几万页,没办法,小编就只能退而求其次,毕竟连普通用户都不能看到的数据,机器也不可能看得到(若有读者知道原因,小编愿闻其详),到这里小编爬取了1980个电影id
#注意在代码中我们每300存储一次links,所以,我们的json文件中实际只有1800个link,但我们的每爬取一个页面,就将该页面电影link输出,若为了完美,读者可将最后输出的180个电影写进去.到这为止我们得到了1980个电影的id,接下来就是使用api爬取
-
- import json
- import time
- import requests
- import re
- import random
- import pymysql
- from bs4 import BeautifulSoup
- import threading
- from fake_useragent import UserAgent
- from queue import Queue
- from selenium import webdriver
- from selenium.webdriver import ActionChains
- #驱动浏览器chrome
- driver = webdriver.Chrome(executable_path = r'C:\Users\Administrator\AppData\Local\Programs\Python\Python36\chromedriver.exe')
- #进入猫眼电影的第二页,读者也可以改为第一页或者其他页
- driver.get('http://maoyan.com/films?showType=3&offset=30')
- links = []
- while True:
- #找到div.movies-list > dl > dd下的所有电影内链接
- movies_list = driver.find_elements_by_css_selector('div.movies-list > dl > dd')
- for link in movies_list:
- #在一些大型网站上,可能会设有蜜獾(在浏览器界面不可见的内链接,但是在网页源代码中石村存在的,如果我们不小心访问了自己的ip可能会被封杀
- # link.is_displayed()可以避免这种情况,若link在浏览器界面是可见的(不是蜜獾),则返回True,若不可见(即为蜜獾,但又存在与html中)则返回False
- if link.is_displayed():
- #获取这个内链接的href属性,输出的形式为http://maoyan.com/films/1331267
- link = link.find_element_by_css_selector('div.movie-item.film-channel > a').get_attribute('href')
- try :
- #使用正则表达式提取films后的内容,形如films/1331267
- link = re.compile('films/\d+').search(link).group()
- #将其存入links中
- links.append(link)
- except :
- #匹配出错,则跳过
- pass
-
- print(len(links))
- print(links[-30:])
- #将存取的links写入links中,若我们每写一个就存入,则有点繁琐,若最后存取,若中间报错,则就白白存取了,
- #这里小编设置了每满300个,就存一次(读者可以自己设置)
- if len(links)%300 == 0:
- with open('maoyan.json','w') as fp:
- json.dump(links,fp)
- #让driver随机的休息几秒,毕竟若不休息,连续的抓取可能会被识别出是恶意的机器人,
- # 还有若网慢了,也可能会因为加载而报错
- time.sleep(random.choice([2.5,3.3,3.9,4.5,4.8,2.9,3.4]))
- #使用ActionChains模拟鼠标点击下一页的内链接
- ActionChains(driver).move_to_element(driver.find_element_by_link_text('下一页')).click().perform()
- #将窗口切换到在点击下一页后出现的窗口中
- windows = driver.window_handles
- driver.switch_to.window(windows[-1])
- #若我们提取的电影id超过10000则跳出循环,否则一直爬取
- if len(links) >= 10000:
- break
- #再次休息,尽可能取演的像真人
- time.sleep(random.choice([ 3.3, 3.9, 4.5, 4.8, 2.9, 3.4])+1)
3.抓取电影信息.电影抓取的步骤以及解释详见下面代码块
这里我们开启的线程,读者若不想开启线程,只需调用函数就可以.若读者需要存取数据,读者需要修改自己连接数据库参数更或者是表结构
- def get_movie_id():
- while True:
- #随机选取一个
- movie_id = random.choice(id)
- #每项的数字
- movie_id = movie_id[6:]
- if movie_id not in visited:
- visited.append(movie_id)
- break
- if len(visited) >= len(id)*0.99:
- #避免程序因为找最后几个,而一直进入死循环里(我们的选取是随机的)
- raise IndexError('max visited')
-
- return movie_id
- def print_data(item_id,item_nm,item_cat,item_distributions,
- item_sc,item_pubDesc,item_rt,item_oriLang,
- item_star,item_ver,item_dra):
- print('id ', item_id)
- print('电影名 ', item_nm)
- print('类型 ', item_cat)
- print('评分 ', item_distributions)
- print('评分 ', item_sc)
- print('上映地点 ', item_pubDesc)
- print('上映时间 ', item_rt)
- print('语种 ', item_oriLang)
- print('演员 ', item_star)
- print('放映版本 ', item_ver)
- print('简介 ', item_dra)
-
- def save_data():
-
- #连接数据库,读者需自己修改user,password,db,port以及Sql语句
- # 连接数据库,读者需自己修改user,password,db,port以及Sql语句
- # 连接数据库,读者需自己修改user,password,db,port以及Sql语句
- #重要事情说三遍
- conn = pymysql.connect(host = 'localhost',port = 3306,
- charset = 'utf8',db = 'scraping',
- user = 'root',password = 'luohong')
-
- cur = conn.cursor()
- while True:
- #若queue有东西就存入数据库
- if not queue.empty():
- try:
-
- value = queue.get()
- cur.execute('insert into maoyan1 values'
- '(%r,%r,%r,%r,%r,%r,%r,%r,%r,%r,%r)'%(value))
- conn.commit()
- except :
- #避免因为字符串长度不够等出现错误,而到导致爬虫错误
- pass
-
-
- def get_movie(thread_name):
- #i记录每个电影成功抓取的数据
- i = 1
- #监视循环
- k = 0
- while k <= 10000 :
- #将读取出的信息,进行正则解析,返回只包含数字的id
- movie_id = get_movie_id()
- #设置cookie以及headers,若读者运行此代码,需要用浏览器随便进入某个猫眼电影的界面,将requests下的cookie以及accept更换一下
- #需要注意的是这里小编用的是chrome浏览器,发现每个电影下的的cookie都是一样的,所以这里cookie没有更改
- #之前小编使用360去查看猫眼电信信息,的cookie是前面都不变,只是改变最后一位数字(1到100内的随机一个数字即可)
- #若读书者使用其他的浏览器的cookie,建议多打开几部,观察cookie的变化规律
- #只用一个也可以,但可能会很危险,但cookie每次报错(可能会出现验证,后续会说)或者重启程序后建议更新cookie
- headers = {
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
- 'Accept-Encoding': 'gzip, deflate, br',
- 'Accept-Language': 'zh-CN,zh;q=0.9',
- 'Cache-Control': 'max-age=0',
- 'Connection': 'keep-alive',
- 'Host': 'm.maoyan.com',
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36',
- 'Cookie': '_lxsdk_cuid=17522383e42c8-02edd93274060a-333376b-144000-17522383e42c8; _lxsdk=F82CAD200D5911EBA49537871E07CB9B88FA743AB2A44D3698ED5D9C226085C2; Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1602596913,1602603790; uuid_n_v=v1; iuuid=337735D0102F11EB926F99D5C5A9852D9F6F125F17BC423AACEFA2A02E9EE547; ci=59%2C%E6%88%90%E9%83%BD; webp=true'
- }
- url = 'https://m.maoyan.com/ajax/detailmovie?movieId={}'.\
- format(movie_id)
- #输出线程的名字,正在访问的url
- print(thread_name,'------',url)
- html = requests.get(url,headers = headers )
- #模拟真人,随机休息,在抓取数据
- time.sleep(random.choice([1,1.5,1.8,2.3,2.6])+2.1)
- if html.status_code == 200:
- try :
- #若访问成功则对其json解析
- html_json = html.json()
- except:
- #有时会跳出滑动验证,会报错,这时我们pass
- pass
- else:
- #若try不报错(即解析成功),才运行else板块
- if len(html_json) > 0:
- for item in html_json:
- #赋值
- item_id = html_json[item].get('id', 'none')
- item_nm = html_json[item].get('nm', 'none')
- item_cat = html_json[item].get('cat', 'none')
- item_distributions = html_json[item].get('distributions', 'none')
- item_sc = html_json[item].get('sc', 'none')
- item_pubDesc = html_json[item].get('pubDesc', 'none')
- item_rt = html_json[item].get('rt', 'none')
- item_oriLang = html_json[item].get('oriLang','none')
- item_star = html_json[item]['shareInfo'].get('star','none')
- item_ver = html_json[item]['shareInfo'].get('ver','none')
- item_dra = html_json[item].get('dra', 'none')
- #打印数据
- print_data(item_id, item_nm, item_cat,
- item_distributions, item_sc,
- item_pubDesc, item_rt, item_oriLang,
- item_star, item_ver, item_dra)
- #将数据put进queue中
- try :
- queue.put((item_id, item_nm, item_cat,
- str(item_distributions), item_sc,
- item_pubDesc, item_rt, item_oriLang,
- item_star, item_ver, item_dra))
- except :
- pass
- #记录有效个数
- print(thread_name, i,'-------',len(visited))
- i += 1
- else:
- print('{}')
- else:
- print('error url')
- print("-------------------------------------------------")
- #随机休息
- time.sleep(random.choice([4.7,5.4,6,4.3,4.8,6.1,7.2,9.8,5.1,5.8]) + 5.4)
- k += 1
- #读取刚才的存放的maoyan.json文件
- with open('maoyan.json','r') as fp:
- content = json.load(fp)
- #这里我们开启了线程抓取数据,加快速度,所以使用了queue使得各个线程之间能够互通信息
- queue = Queue()
- #存取每个线程访问过的网站,避免其他线程再次抓取
- visited = []
- id = content.copy()[1000:]
- #若不想调用线程,则直接调用函数就可以了,不过特别慢
- #开启线程,之前小编没有开启线程,试着去抓取那速度很慢,时间过了半小时左右,就会报错(出现滑动验证)
- #在开启5个线程后,也是差不多半小时才报错,所以小编猜测他的反爬机制是达到半小时出现滑动验证,与数量无关
- #在我开启了15个线程后,避免报错重复抓取对id分成了0-500,500-1000,1000-1980三个区间抓取,就没有报错了,顺利抓取了,很幸运
- #读者可以尝试开启多线程抓取,若出现滑动验证,只需打开浏览器访问刚才出现滑动验证的url,手动验证后,重启程序就可以继续爬取(所以分区间很重要,和线程数量)
- threading.Thread(target = get_movie,args = ('THREAD1 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD2 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD3 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD4 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD5 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD6 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD7 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD8 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD9 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD10 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD11 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD12 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD13 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD14 ',)).start()
- threading.Thread(target = get_movie,args = ('THREAD15 ',)).start()
- #开启存储数据线成,若读者想将其存入自己的数据库,去掉#号就可以运行(不过需要输入自己的user,password,db等)
- # threading.Thread(target = save_data).start()
4.小编开启了15个线程后,差不多20分钟就可以搞定了,读者可以自行添加线程(厚道点,哈哈哈)
下面是部分结果
#由于小编边写代码,编写中文,所以符号之内的都用了英文符号,大家见谅
#若代码中有任何错误.希望大家可以积极提出,小编尽力修改
#转发者请标明文章出处
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。