今天的文比較長:加代碼一共8296字,不算代碼一共:3746.
閱讀時(shí)間較長开缎,內(nèi)容僅做參考,
之前看了不少大廠對實(shí)習(xí)生的招聘要求林螃,對python實(shí)習(xí)生的要求中都要求要有爬蟲編寫的經(jīng)驗(yàn)奕删,這兩周的爬蟲項(xiàng)目讓我學(xué)到了很多,所以不論是為了入門疗认,還是為了提高完残,寫寫小型的項(xiàng)目總是很有用的。
希望用到的技能:
- 爬蟲基礎(chǔ)知識
- BeautifulSoup的使用
- 多進(jìn)程
- 操作數(shù)據(jù)庫
- 使用隊(duì)列
- 文件操作
- MATLAB畫圖統(tǒng)計(jì)横漏,實(shí)現(xiàn)數(shù)據(jù)可視化
- 下載圖片
- 正則表達(dá)式
- 使用代理池谨设、防止封鎖
- 克服反爬機(jī)制等
在寫爬蟲之前我是希望用到這些知識的,有些我了解缎浇,有些沒接觸過扎拣,加粗的是這次用到的,下面來一步一步開始我們的爬蟲素跺,我會隨時(shí)介紹我在編寫的時(shí)候遇到的問題二蓝。
先來看下成果:
還有數(shù)據(jù)庫,我用的數(shù)據(jù)可視化工具Navicat:
很多爬蟲教程都是在爬妹子圖指厌、美女圖刊愚、啥啥圖,這些對身體不好仑乌。不過我也幫室友爬過他們感興趣的網(wǎng)站百拓,就是那個(gè)解數(shù)學(xué)題的那個(gè)。我們正經(jīng)一點(diǎn)晰甚!
首先豆瓣網(wǎng)站對與爬蟲工程還是十分友好的衙传,在寫爬蟲之前,我們先來看一下這個(gè)網(wǎng)站的robots.txt文件:https://movie.douban.com/robots.txt
User-agent: *
Disallow: /subject_search
Disallow: /amazon_search
Disallow: /search
Disallow: /group/search
Disallow: /event/search
Disallow: /celebrities/search
Disallow: /location/drama/search
Disallow: /forum/
Disallow: /new_subject
Disallow: /service/iframe
Disallow: /j/
Disallow: /link2/
Disallow: /recommend/
Disallow: /doubanapp/card
Sitemap: https://www.douban.com/sitemap_index.xml
Sitemap: https://www.douban.com/sitemap_updated_index.xml
# Crawl-delay: 5
User-agent: Wandoujia Spider
Disallow: /
從這份文件中厕九,我們可以看到:User-agent: *蓖捶,這是指的所有的爬蟲,它下面的Disallow后面的所有的文件夾都不允許爬扁远。# Crawl-delay: 5這是說的是爬蟲的延時(shí)俊鱼,不能太快,不然會增加豆瓣服務(wù)器的負(fù)擔(dān)畅买。我們?yōu)榱藢W(xué)習(xí)當(dāng)然是不會故意為難人家的服務(wù)器并闲。
User-agent: Wandoujia Spider
Disallow: /
這兩句就有意思了,豌豆莢的爬蟲是不允許爬所有的文件的谷羞。
當(dāng)然上面說的所有的規(guī)定都只是規(guī)定帝火,你如果非要爬,那也不是做不到湃缎,但是沒有必要犀填。我們沒必要跟別人作對,如果你真的爬到了什么不該知道的東西嗓违,那你可能就涼了九巡。爬蟲禮儀還是很重要的!
所以我們先去看了我們需要的內(nèi)容在哪個(gè)文件夾下:
250電影列表:https://movie.douban.com/top250
——/top250/
電影詳細(xì)信息:https://movie.douban.com/subject/1292052/
——/subject/
電影海報(bào):https://movie.douban.com/photos/photo/480747492/
——/photos/
這三個(gè)目錄在上面的robotsx.txt文件是沒有禁止的蹂季,所以可以爬取冕广,但是delay是要求5秒。
乏盐。佳窑。。父能。神凑。。這有點(diǎn)長了何吝,那理想情況:5250=1250s,在加上30張海報(bào)溉委,530*250=37500s,這得猴年馬月?爱榕?所以我都設(shè)置為了1s瓣喊,而且中間加上了一些BeautifulSoup的處理時(shí)間,差不多了黔酥。
robots.txt看完了藻三,接下來就是應(yīng)該分析我們需要哪些步驟來完成整個(gè)項(xiàng)目洪橘,說實(shí)話,這個(gè)項(xiàng)目不算大棵帽,但是也不小熄求,算是目前我寫過的最大的爬蟲了。逗概。弟晚。。
看一下首頁:
頁面地址非常簡單逾苫。這是第一頁卿城,每一部電影的排名,電影名稱等信息都是以一個(gè)長方形的item展示在頁面上
在首頁的下面可以清楚的看到每一頁25部電影铅搓,一共10頁瑟押,一共250個(gè)items。非常規(guī)范星掰,這對我們編寫爬蟲是非常有利的勉耀。
每部電影的詳情頁的地址非常清晰,頁面上的信息也非常豐富蹋偏,基本上這一頁能看到的東西便斥,我都想拿下來。
再來看一下海報(bào)的地址威始,瀏覽器直接點(diǎn)擊電影封面就行:
可以發(fā)現(xiàn)一部的電影的照片還是不少的枢纠,No.1就有535張,全部爬下來都行黎棠,但是沒有必要晋渺,所以我們就取30張海報(bào)就行。
好了脓斩,我們在瀏覽器里用眼睛看到了我們想要的東西木西,電影信息,電影海報(bào)随静,這是我們需要的八千。
網(wǎng)站的結(jié)構(gòu)是這樣的
排行榜——10頁
詳情頁面——一頁
海報(bào)頁面——有多頁(只取30張)
所以根據(jù)網(wǎng)頁結(jié)構(gòu),以及我們需要的數(shù)據(jù)內(nèi)容燎猛,可以將獲取信息的步驟分為:
步驟:
- 爬取10頁排行榜地址
- 爬取所有250部電影詳情頁地址
- 處理每一部電影
3.1 爬取詳情頁
3.1 清洗數(shù)據(jù)
3.2 存儲數(shù)據(jù)
3.3 爬取電影海報(bào)頁面的30張海報(bào)的地址
3.4 下載海報(bào)
函數(shù)式編程:
大概的步驟是這樣的恋捆,因此我們就可以先寫出main函數(shù):
def main():
#1. 爬取10頁排行榜地址
ten_url=get_ten_page_url()
#2. 爬取所有250部電影詳情頁地址
movie250infos_url=get_250movie_info_url(ten_url)
#3. 處理每一部電影
for i in movie250infos_url:
#3.1 爬取詳情頁
info=get_info(i)
#3.1 清洗數(shù)據(jù)
info=configue_info(info)
#3.2 存儲數(shù)據(jù)
save_info(info)
#3.3 爬取電影海報(bào)頁面的30張海報(bào)的地址
img=get_img(info)
#3.4 下載海報(bào)
save_img(img)
接下來只要完成每一個(gè)函數(shù)的功能即可:
1. 爬取10頁排行榜地址
有10頁,所以現(xiàn)在來看一下下一頁的地址是怎樣的:
可以看出重绷,
https://movie.douban.com/top250?start=25&filter=
這地址還是非常清楚的沸停,start=25,filter=昭卓。這意思就是是從第25號開始愤钾,過濾器值為空瘟滨。
這里就有問題了,那第一頁的地址和第二頁的地址結(jié)構(gòu)是不一樣的能颁,而且明明第一頁有25部電影室奏,他為什么是start=25?劲装?,這對學(xué)編程的人來說昌简,顯而易見占业,我們數(shù)數(shù)是從0開始的,第一部0纯赎,第二部是1谦疾,第三部是2....,以此類推,所以我?guī)е闷嫘脑嚵诉@個(gè)地址:https://movie.douban.com/top250?start=0&filter=
哈哈犬金。跟之前的/top250/顯示的是一樣的念恍。那么filter=是什么意思呢?
可以看到第一部電影的item的右上角晚顷,有一個(gè)我沒看過的選項(xiàng)峰伙,這應(yīng)該就是需要過濾的東西,但是這里對我們沒有影響该默,所以我們可以很容易的得到全部10頁排行榜的地址:
#獲得10頁包含250個(gè)簡介的頁面地址
def get_ten_pageurl():
#array=["https://movie.douban.com/top250?start=0&filter="]
array=[]
for i in range(0,250,25):
array.append("https://movie.douban.com/top250?start="+str(i)+"&filter=")
return array
這里可以發(fā)現(xiàn)瞳氓,#array=["https://movie.douban.com/top250?start=0&filter="]
這一句是我用來測試代碼用的。因?yàn)樗ㄐ洌还?0頁匣摘,你在后面試運(yùn)行時(shí),不可能每次都從頭開始裹刮,這樣會浪費(fèi)很多時(shí)間音榜。你可以像我一樣,將一些需要循環(huán)的處理的列表設(shè)置成只有一個(gè)值捧弃,這樣就不會等到先處理完10頁鏈接赠叼,在執(zhí)行你需要的地方。這是一個(gè)調(diào)試的好方法违霞,但是在最后的時(shí)候梅割,你要把他注釋掉,別忘記了葛家。
這里我們是非常幸運(yùn)的户辞,并沒有遇到用ajax來換頁的情況,在之前我爬取拉勾網(wǎng)時(shí)癞谒,就遇到了底燎,下一頁的地址和上一頁的地址是一樣的刃榨,他是通過ajax動態(tài)交互技術(shù)實(shí)現(xiàn)的,所以獲取下一頁的地址就成了問題双仍。當(dāng)然解決它也是很容易的枢希,等下次寫爬蟲我在詳細(xì)介紹ajax頁面的爬取。
2. 爬取所有250部電影詳情頁地址
前面的10頁的地址的獲取不算是爬蟲朱沃,只能算是一個(gè)簡單的for循環(huán)苞轿。下面我們來看一看如何爬取250部電影的詳情頁地址:
先來看一看網(wǎng)頁的代碼,分析下結(jié)構(gòu)逗物,打開我們要爬取的頁面按F12搬卒,或者直接在items上右鍵->檢查元素:
就會出現(xiàn)下面的情況:
可以看到直接找到了我們需要的地址。
當(dāng)然翎卓,如果你按的F12契邀,出來的不是這樣的,你需要點(diǎn)擊元素
然后點(diǎn)擊這個(gè)
然后將鼠標(biāo)移到電影信息上失暴,單擊坯门。就會出來一樣的東西。
然后我們來看一下我們需要的詳情頁的地址在哪個(gè)位置:
可以看出逗扒,整個(gè)頁面是用
<ol>
<li><\li>
<li><\li>
<li><\li>
<li><\li>
<li><\li>
<\ol>
這樣的結(jié)構(gòu)古戴,每一個(gè)<li>標(biāo)簽里包含一個(gè)div標(biāo)簽,里面又有pic和info兩個(gè)標(biāo)簽
展開里面的全部標(biāo)簽矩肩,可以看出這里包含了我們需要的大部分信息允瞧,但是還不夠,我們需要像詳情頁里那樣詳細(xì)的信息蛮拔,不過述暂,這里的信息我們頁可以保存一部分。這里我只拿了排名建炫、電影名畦韭、電影詳情頁地址,這三個(gè)信息肛跌。觀察html文件可以發(fā)現(xiàn)艺配,這三個(gè)信息都在class="pic"的a標(biāo)簽里,所以只要取這一小段就行衍慎,然后獲取信息就行转唉。利用BeautifulSoup將頁面上的25部電影的信息篩選出來,然后for循環(huán)處理稳捆,提取信息保存赠法。
# 得到每一個(gè)電影的詳情頁地址
def get_250movie_page_url(ten_pageurl,Directory):
"""
輸入10頁地址
將top250 的電影首頁地址保存下來,同時(shí)存到隊(duì)列中和本地text
"""
if not os.path.exists(Directory):
os.makedirs(Directory)
url_queue=MyQueue()
conn = mysql.connector.connect(user='root', password='password', database='douban250')
cursor = conn.cursor()
conn.commit()
for page_url in ten_pageurl:
try:
html = urlopen(page_url)
bsobj = BeautifulSoup(html, features="html.parser")
# 得到當(dāng)前頁面上25個(gè)包含序號乔夯、詳情頁的地址的div標(biāo)簽砖织,存為列表
movie_info_items = bsobj.findAll("div", {"class": "pic"})
for movie_info in movie_info_items:
try:
movie_id = int(movie_info.find("em").get_text())
movie_info_url = movie_info.find("a").attrs["href"]
movie_name=movie_info.find("img").attrs["alt"]
url_queue.push(movie_info_url)
with open(Directory + "/250homepage_url.txt", "a") as f:
f.write(str(movie_id))
f.write("\t")
f.write(movie_name)
f.write("\t")
f.write(movie_info_url)
f.write("\n")
print("獲取movie%s詳情頁url成功"%(movie_id))
cursor.execute('insert into movie_250url '
'(id, name,url) '
'values (%s, %s,%s)',
[movie_id, movie_name, movie_info_url])
conn.commit()
except:
print("獲取movie詳情頁url失斂钋帧!")
#continue
time.sleep(2)
except:
print("頁面%s處理失敗"%(page_url))
time.sleep(1)
continue
cursor.close()
return url_queue
可以發(fā)現(xiàn)這個(gè)函數(shù)很長侧纯,因?yàn)檫@里包含了很多操作新锈。這里我將250個(gè)信息存在了隊(duì)列里,因?yàn)榭舭荆也淮蛩闶褂枚嗑€程了妹笆。存在隊(duì)列里,隊(duì)列先進(jìn)先出娜氏,沒毛病拳缠。然后用了os新建文件夾。用了數(shù)據(jù)庫牍白,將排名、電影名抖棘、地址存為一張數(shù)據(jù)表茂腥。用了txt文件讀寫,將這些信息保存在了txt文本中切省。用了try最岗。。朝捆。except般渡。。芙盘。語句錯(cuò)誤調(diào)試驯用,以防突發(fā)問題,從而不影響整個(gè)程序儒老。用了time模塊蝴乔,爬蟲間隔2s。最后返回一個(gè)隊(duì)列驮樊。
可以發(fā)現(xiàn)我用的是urllib的urlopen薇正,而不是requests的get。這個(gè)問題以及BeautifulSoup的使用我以后會單獨(dú)拿出來寫囚衔。
3. 處理每一部電影
得到了250個(gè)頁面的地址挖腰,都存在隊(duì)列里。接下來處理他們练湿。
3.1 爬取詳情頁
我直接將隊(duì)列的頭元素傳入函數(shù)猴仑,獲取頁面,并新建為一個(gè)BeautifulSoup對象返回
#獲取詳情頁
def get_movie_info(url):
try:
html = urlopen(url)
bsobj = BeautifulSoup(html, features="html.parser")
print("獲取詳情頁面成功",url[-8:])
time.sleep(1)
return bsobj
except:
print("詳情頁面%s獲取失敗"%(url[-8:]))
time.sleep(1)
return None
bsobj = BeautifulSoup(html, features="html.parser")
這我現(xiàn)在還不太清楚肥哎,這是運(yùn)行時(shí)加的——features="html.parser"宁脊,沒細(xì)看断国。但是不加會給個(gè)警告warning。
3.1 清洗數(shù)據(jù)
得到了bs對象榆苞,接下來就是處理他了稳衬。
詳情頁的結(jié)構(gòu)比前面的250電影的結(jié)構(gòu)要復(fù)雜很多,這里花了我不少時(shí)間坐漏,到現(xiàn)在還有三個(gè)沒有解決:制片國家薄疚、語言、電影別名赊琳。這三個(gè)的html的結(jié)構(gòu)有點(diǎn)奇怪街夭。而且BeautifulSoup處理信息的速度并不快,所以就沒管它了躏筏,等下次將BeautifulSoup板丽、xpath等工具時(shí)在來處理這個(gè)問題,這里我將他們作為一個(gè)列表反回了趁尼,方便后面的處理埃碱。
#處理詳情頁
def configure_infopage(bsobj):
try:
#電影排名:
movie_id=bsobj.find("span", {"class": "top250-no"}).get_text()
# 得到當(dāng)前頁面上的電影名稱
movie_name = bsobj.find("span", {"property": "v:itemreviewed"}).get_text()
#電影簡介
movie_intro=bsobj.find("span",{"property":"v:summary"}).get_text().replace("\n \u3000\u3000","").replace(" ","")
#獲取電影海報(bào)頁面鏈接
movie_photos=bsobj.find("a",{"class":"nbgnbg"}).attrs["href"]
#電影詳細(xì)信息左
movie_info_items=bsobj.find("div",{"id":"info"})
#處理電影詳細(xì)信息
#導(dǎo)演: 弗蘭克·德拉邦特
movie_directer = movie_info_items.find("a", {"rel": "v:directedBy"}).get_text()
#編劇: 弗蘭克·德拉邦特 / 斯蒂芬·金
movie_attrs =movie_info_items.find_all("a",{"href":re.compile("/celebrity"),})[0].get_text()+"|"+ \
movie_info_items.find_all("a", {"href": re.compile("/celebrity"), })[1].get_text()
#主演: 蒂姆·羅賓斯 / 摩根·弗里曼 / 鮑勃·岡頓 / 威廉姆·賽德勒 / 克蘭西·布朗 / 吉爾·貝羅斯 / 馬克·羅斯頓 / 詹姆斯·惠特摩 / 杰弗里·德曼 / 拉里·布蘭登伯格 / 尼爾·吉恩托利 / 布賴恩·利比 / 大衛(wèi)·普羅瓦爾 / 約瑟夫·勞格諾 / 祖德·塞克利拉 / 保羅·麥克蘭尼 / 芮妮·布萊恩 / 阿方索·弗里曼 / V·J·福斯特 / 弗蘭克·梅德拉諾 / 馬克·邁爾斯 / 尼爾·薩默斯 / 耐德·巴拉米 / 布賴恩·戴拉特 / 唐·麥克馬納斯
# 取前3
movie_actors=""
for i in movie_info_items.find_all("a",{"rel":"v:starring"})[:5]:
movie_actors=movie_actors+i.get_text()
#類型: 劇情 / 犯罪
movie_genre=movie_info_items.find_all("span",{"property":"v:genre"})[0].get_text()+"|"+ \
movie_info_items.find_all("span", {"property": "v:genre"})[1].get_text()
#制片國家/地區(qū): 美國
movie_saition=None
#語言: 英語
movie_language=None
#上映日期: 1994-09-10(多倫多電影節(jié)) / 1994-10-14(美國)
movie_initialReleaseDate =""
for i in movie_info_items.find_all("span", {"property": "v:initialReleaseDate"}):
movie_initialReleaseDate=movie_initialReleaseDate+i.get_text()
#片長: 142分鐘
movie_Runtime=movie_info_items.find("span",{"property":"v:runtime"}).get_text()
#又名: 月黑高飛(港) / 刺激1995(臺) / 地獄諾言 / 鐵窗歲月 / 消香克的救贖
movie_alias=None
#IMDb鏈接: tt0111161
movie_IMDb_url=movie_info_items.find("a",{"rel":"nofollow"}).attrs["href"]
#電影評分信息
movie_votes_info=bsobj.find("div",{"id":"interest_sectl"})
#處理電影評分信息
#評分
movie_vote=movie_votes_info.find("strong",{"class":"ll rating_num"}).get_text()
#評分人數(shù)
movie_voters=movie_votes_info.find("span",{"span":"v:votes"})
#5星數(shù)量
movie_vote_5_stars=movie_votes_info.find_all("span",{"class":"rating_per"})[0].get_text()
print("正在解析電影信息",movie_id)
except:
print("電影信息解析失敗")
return None
return [movie_id,
movie_name,
movie_vote,
movie_voters,
movie_vote_5_stars,
movie_directer,
movie_attrs,
movie_actors,
movie_genre,
movie_saition,
movie_language,
movie_initialReleaseDate,
movie_Runtime,
movie_alias,
movie_IMDb_url,
movie_intro,
movie_photos]
3.2 存儲數(shù)據(jù)
#存儲信息
def save_infos(infos,Directory):
filepath = Directory + "/info/"
if not os.path.exists(filepath):
os.makedirs(filepath)
conn = mysql.connector.connect(user='root', password='password', database='douban250')
cursor = conn.cursor()
print("-"*20)
print("正在將movie%s信息存到數(shù)據(jù)庫。酥泞。砚殿。。"%(infos[0]))
cursor.execute('insert into movie_250_info \
(\
movie_id,\
movie_name,\
movie_vote,\
movie_voters,\
movie_vote_5_stars,\
movie_directer,\
movie_attrs,\
movie_actors,\
movie_genre,\
movie_saition,\
movie_language,\
movie_initialReleaseDate,\
movie_Runtime,\
movie_alias,\
movie_IMDb_url,\
movie_intro,\
movie_photos\
) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)', infos)
conn.commit()
conn.close()
print("正在將文件存儲為txt芝囤。似炎。。悯姊。羡藐。")
with open(filepath+ infos[0][3:] + ".txt", "a") as fi:
for i in infos:
if type(i) == list:
for x in i:
fi.write(x)
fi.write("\t")
continue
fi.write(i if i is not None else "None")
fi.write("\n")
print("存儲完成!")
print("-" * 20)
在這一步悯许,數(shù)據(jù)庫的插入工作也廢了不少時(shí)間传睹,首先這是我第一次使用數(shù)據(jù)庫,對于數(shù)據(jù)庫的設(shè)計(jì)還一知半解岸晦,這也放之后單講欧啤,今天上午我在寫關(guān)于數(shù)據(jù)庫的文了。應(yīng)該很快了启上。這里我也存了一份文件作為參考你辣。
3.3 爬取電影海報(bào)頁面的30張海報(bào)的地址
可以看到座慰,我將電影的海報(bào)地址作為列表的最后一項(xiàng)尚揣。這是只要用[-1]就能取到這個(gè)值站玄。
# 獲取海報(bào)頁面前30張圖片的地址
def get_img_url(movie_url):
html=urlopen(movie_url)
bsobj=BeautifulSoup(html, features="html.parser")
img_url=bsobj.find_all("div",{"class":"cover"})
l=[]
for i in img_url:
url=i.find("img").attrs["src"]
l.append(url)
#print("-"*20)
print("30張海報(bào)地址獲取成功!")
return l
3.4下載海報(bào)
下載就很簡單了,我沒有用requests的保存二進(jìn)制文件來保存文件纫谅,用的是urllib.urlretrieve,這個(gè)很好用炫贤。
#下載圖片
def save_img(infos,Directory):
img = get_img_url(infos[-1])
filepath=Directory + "/pic/" + infos[0] + "/"
print("-" * 20)
print("海報(bào)儲存在",filepath)
#print("-" * 20)
#print("\n")
for i in img:
download_img(i, filepath)
print("海報(bào)30張下載成功")
print("-" * 20)
# 下載海報(bào)
def download_img(url,download_directory):
"""
保存海報(bào)
輸入:下載文件地址,和板存路徑
輸出:將文件保存到相應(yīng)文件下
"""
if not os.path.exists(download_directory):
os.makedirs(download_directory)
file_path=download_directory+url[-14:]
try:
urlretrieve(url, file_path)
#print("1")
print("下載圖片:%s完成付秕!" % (url[-14:]))
time.sleep(1)
except :
print("下載圖片:%s失斃颊洹!" % (url[-14:]))
return None
我把他們分開寫成了兩個(gè)函數(shù)询吴。我的老師跟我說掠河,一個(gè)函數(shù)不應(yīng)該很長。這次寫爬蟲中并沒有注意這個(gè)問題猛计,下次一定注意唠摹。而且我函數(shù)的分工還是有些冗余,不明確奉瘤。這是一個(gè)缺點(diǎn)勾拉。
按照我們前面的分析,到這里就結(jié)束了盗温。但是開頭的main函數(shù)是我剛寫的藕赞,之前并沒這么想。
我的main函數(shù):
def main():
Directory = "C:/Users/葛蘇健/Desktop/py/python小案例/004爬取豆瓣新片"
print("正在獲取10頁目錄肌访。找默。艇劫。吼驶。。")
ten_pageUrl = get_ten_pageurl()
print("正在連接數(shù)據(jù)庫店煞。蟹演。。顷蟀。酒请。")
conn = mysql.connector.connect(user='root', password='password', database='douban250')
cursor = conn.cursor()
cursor.execute('create table movie_250url '
'(id int primary key, '
'name varchar(20), '
'url char(50))')
cursor.execute('create table movie_250_info (\
movie_id char(10) primary key,\
movie_name text,\
movie_vote char(20),\
movie_voters char(20),\
movie_vote_5_stars char(20),\
movie_directer text,\
movie_attrs text,\
movie_actors text,\
movie_genre text,\
movie_saition text NULL,\
movie_language text NULL,\
movie_initialReleaseDate text,\
movie_Runtime text,\
movie_alias text NULL,\
movie_IMDb_url text,\
movie_intro text,\
movie_photos text\
)')
conn.commit()
time.sleep(1)
print("連接成功!")
print("正在獲取250個(gè)電影的詳情頁url鸣个。羞反。。囤萤。")
movieInfoPage_queue = get_250movie_page_url(ten_pageUrl, Directory)
print("獲取250個(gè)電影的詳情頁url成功昼窗!")
time.sleep(1)
# movieInfoPage_queue =MyQueue()
# movieInfoPage_queue.destroy()
# movieInfoPage_queue.push("https://movie.douban.com/subject/1291546/")
# # #info_path = Directory + "/info.txt"
print("正在創(chuàng)建文件夾")
if not os.path.exists(Directory):
os.makedirs(Directory)
print("創(chuàng)建成功")
print("開始爬取top250電影:")
print("#"*50)
while not movieInfoPage_queue.isEmpty():
try:
print("=" * 50)
print("正在獲取頁面。涛舍。澄惊。")
info = get_movie_info(movieInfoPage_queue.top())
movieInfoPage_queue.pop()
print("開始解析電影詳情頁。。掸驱。")
infos = configure_infopage(info)
print("解析電影詳情頁成功肛搬!")
save_infos(infos,Directory)
print("開始下載海報(bào):")
save_img(infos,Directory)
print("海報(bào)下載完成\n")
print("電影%s爬取成功!"%(infos[0]))
print("=" * 50)
print("\n")
except:
#print("=" * 50)
print("失敱显簟温赔!")
print("=" * 50)
print("\n")
continue
....廢話非常多,是因?yàn)槲蚁肓私馀老x每時(shí)每刻都在做什么帅刀。
下面來總結(jié)下:
這次爬蟲用到了beautifulsoup让腹、os、re扣溺、time骇窍、urllib.urlopen、mysql.connector锥余、urllib.urlretrieve這里個(gè)庫腹纳,高級的技術(shù)也沒用到。
這次學(xué)會的:數(shù)據(jù)庫交互驱犹。嘲恍。。其他的都只是鞏固雄驹,而且這次的數(shù)據(jù)庫操作也不是很復(fù)雜佃牛,數(shù)據(jù)可視化還沒有完成,代理医舆、反爬都沒遇到過俘侠。。蔬将。爷速。
但是我還是鞏固了基礎(chǔ),對爬蟲的整體架構(gòu)有了更深入的理解霞怀。之后我會單獨(dú)將這次用到的技術(shù)拿出來寫惫东,以此來鞏固。
那再來說說這次的不足:
代碼冗余毙石,不夠精簡廉沮、層次不清晰、寫代碼前的準(zhǔn)備不夠充分徐矩、函數(shù)式編程設(shè)計(jì)模式不熟悉滞时,沒有章法。我應(yīng)該去看看設(shè)計(jì)模式方面的東西——spring MVC啥的丧蘸。
同時(shí)這次的爬蟲速度慢漂洋、空間使用量大遥皂。我昨晚上仔細(xì)看了看python的垃圾回收機(jī)制,收獲不少——引用計(jì)數(shù)刽漂、代回收Generational GC演训,前者用于大多數(shù)情況,后專為循環(huán)引用設(shè)計(jì)贝咙,詳細(xì)見python垃圾回收
寫的很好样悟。這次的爬蟲還是有點(diǎn)問題,250部爬下來160部庭猩,剩下的都是因?yàn)樘幚硇畔r(shí)以及電腦自身問題窟她,爬蟲是在昨天運(yùn)行的,寫完文章后便沒理它蔼水,一直運(yùn)行震糖,結(jié)果電腦頻繁睡眠,電腦的wifi一直斷趴腋,還好設(shè)置了等待吊说,不然直接出錯(cuò)就全涼了,后來在我自己監(jiān)督下還是斷了幾次优炬,所以只爬了一半多颁井,當(dāng)然我覺得大部分還是代碼的問題,存數(shù)據(jù)庫蠢护,數(shù)據(jù)清洗的地方有問題雅宾,因?yàn)槊坎侩娪暗脑斍轫撋系男畔⒉灰粯樱抑粎⒄樟藥讉€(gè)葵硕,剩下的就是格式問題眉抬,因?yàn)闀r(shí)間問題,僅做學(xué)習(xí)用贬芥,也就沒去找那幾頁的差距吐辙。
好的就這樣宣决,下周末繼續(xù)蘸劈,下面的代碼,庫安裝完成后可以直接運(yùn)行尊沸。
全部代碼:
# 爬取要求:
# 目標(biāo)豆瓣top250
# 結(jié)果:電影名威沫,排名,導(dǎo)演洼专,主演棒掠,評分,年代屁商,分類烟很,多少人評價(jià),一句概括,電影海報(bào)雾袱。
# 用數(shù)據(jù)庫儲存
# 知識點(diǎn):
# 爬蟲基礎(chǔ)
# 數(shù)據(jù)庫
# 用隊(duì)列存儲未完成的標(biāo)題
# 可以考慮多進(jìn)程
import time
import os
import re
import mysql.connector
#考慮使用xpath
from bs4 import BeautifulSoup
from urllib.request import urlopen
from urllib.request import urlretrieve
#我用的自己寫的隊(duì)列恤筛,模塊QUEUE使用不熟練
class LNode:
def __init__(self,arg):
self.data=arg
self.next=None
class MyQueue:
#模擬隊(duì)列
def __init__(self):
#phead=LNode(None)
self.data=None
self.next=None
self.front=self#指向隊(duì)列首
self.rear=self#指向隊(duì)列尾
#判斷隊(duì)列是否為空,如果為空返回True,否則返回false
def isEmpty(self):
return self.front==self.rear
#返回隊(duì)列的大小
def size(self):
p=self.front
size=0
while p.next!=self.rear.next:
p=p.next
size+=1
return size
#返回隊(duì)列首元素
def top(self):
if not self.isEmpty():
return self.front.next.data
else:
#print("隊(duì)列為空")
return None
#返回隊(duì)列尾元素
def bottom(self):
if not self.isEmpty():
return self.rear.data
else:
#print("隊(duì)列為空")
return None
#出隊(duì)列
def pop(self):
if self.size()==1:
data=self.front.next
self.rear=self
return data.data
elif not self.isEmpty():
data=self.front.next
self.front.next=self.front.next.next
#print("出隊(duì)列成功")
return data.data
else:
#print("隊(duì)列已為空")
return None
#入隊(duì)列
def push(self,item):
tmp=LNode(item)
self.rear.next=tmp
self.rear=self.rear.next
#print("入隊(duì)列成功")
#清空隊(duì)列
def destroy(self):
self.next=None
#print("隊(duì)列已清空")
#打印隊(duì)列
def showQueue(self):
if not self.isEmpty():
p=self.front.next
while p != self.rear.next:
print(p.data)
p=p.next
#獲得10頁包含250個(gè)簡介的頁面地址
def get_ten_pageurl():
#array=["https://movie.douban.com/top250?start=0&filter="]
array=[]
for i in range(0,250,25):
array.append("https://movie.douban.com/top250?start="+str(i)+"&filter=")
return array
# 得到每一個(gè)電影的詳情頁地址
def get_250movie_page_url(ten_pageurl,Directory):
"""
輸入10頁地址
將top250 的電影首頁地址保存下來芹橡,同時(shí)存到隊(duì)列中和本地text
"""
if not os.path.exists(Directory):
os.makedirs(Directory)
url_queue=MyQueue()
conn = mysql.connector.connect(user='root', password='password', database='douban250')
cursor = conn.cursor()
conn.commit()
for page_url in ten_pageurl:
try:
html = urlopen(page_url)
bsobj = BeautifulSoup(html, features="html.parser")
# 得到當(dāng)前頁面上25個(gè)包含序號毒坛、詳情頁的地址的div標(biāo)簽,存為列表
movie_info_items = bsobj.findAll("div", {"class": "pic"})
for movie_info in movie_info_items:
try:
movie_id = int(movie_info.find("em").get_text())
movie_info_url = movie_info.find("a").attrs["href"]
movie_name=movie_info.find("img").attrs["alt"]
url_queue.push(movie_info_url)
with open(Directory + "/250homepage_url.txt", "a") as f:
f.write(str(movie_id))
f.write("\t")
f.write(movie_name)
f.write("\t")
f.write(movie_info_url)
f.write("\n")
print("獲取movie%s詳情頁url成功"%(movie_id))
cursor.execute('insert into movie_250url '
'(id, name,url) '
'values (%s, %s,%s)',
[movie_id, movie_name, movie_info_url])
conn.commit()
except:
print("獲取movie詳情頁url失斄炙怠煎殷!")
#continue
time.sleep(2)
except:
print("頁面%s處理失敗"%(page_url))
time.sleep(1)
continue
cursor.close()
return url_queue
#獲取詳情頁
def get_movie_info(url):
try:
html = urlopen(url)
bsobj = BeautifulSoup(html, features="html.parser")
print("獲取詳情頁面成功",url[-8:])
time.sleep(1)
return bsobj
except:
print("詳情頁面%s獲取失敗"%(url[-8:]))
time.sleep(1)
return None
#處理詳情頁
def configure_infopage(bsobj):
try:
#電影排名:
movie_id=bsobj.find("span", {"class": "top250-no"}).get_text()
# 得到當(dāng)前頁面上的電影名稱
movie_name = bsobj.find("span", {"property": "v:itemreviewed"}).get_text()
#電影簡介
movie_intro=bsobj.find("span",{"property":"v:summary"}).get_text().replace("\n \u3000\u3000","").replace(" ","")
#獲取電影海報(bào)頁面鏈接
movie_photos=bsobj.find("a",{"class":"nbgnbg"}).attrs["href"]
#電影詳細(xì)信息左
movie_info_items=bsobj.find("div",{"id":"info"})
#處理電影詳細(xì)信息
#導(dǎo)演: 弗蘭克·德拉邦特
movie_directer = movie_info_items.find("a", {"rel": "v:directedBy"}).get_text()
#編劇: 弗蘭克·德拉邦特 / 斯蒂芬·金
movie_attrs =movie_info_items.find_all("a",{"href":re.compile("/celebrity"),})[0].get_text()+"|"+ \
movie_info_items.find_all("a", {"href": re.compile("/celebrity"), })[1].get_text()
#主演: 蒂姆·羅賓斯 / 摩根·弗里曼 / 鮑勃·岡頓 / 威廉姆·賽德勒 / 克蘭西·布朗 / 吉爾·貝羅斯 / 馬克·羅斯頓 / 詹姆斯·惠特摩 / 杰弗里·德曼 / 拉里·布蘭登伯格 / 尼爾·吉恩托利 / 布賴恩·利比 / 大衛(wèi)·普羅瓦爾 / 約瑟夫·勞格諾 / 祖德·塞克利拉 / 保羅·麥克蘭尼 / 芮妮·布萊恩 / 阿方索·弗里曼 / V·J·福斯特 / 弗蘭克·梅德拉諾 / 馬克·邁爾斯 / 尼爾·薩默斯 / 耐德·巴拉米 / 布賴恩·戴拉特 / 唐·麥克馬納斯
# 取前3
movie_actors=""
for i in movie_info_items.find_all("a",{"rel":"v:starring"})[:5]:
movie_actors=movie_actors+i.get_text()
#類型: 劇情 / 犯罪
movie_genre=movie_info_items.find_all("span",{"property":"v:genre"})[0].get_text()+"|"+ \
movie_info_items.find_all("span", {"property": "v:genre"})[1].get_text()
#制片國家/地區(qū): 美國
movie_saition=None
#語言: 英語
movie_language=None
#上映日期: 1994-09-10(多倫多電影節(jié)) / 1994-10-14(美國)
movie_initialReleaseDate =""
for i in movie_info_items.find_all("span", {"property": "v:initialReleaseDate"}):
movie_initialReleaseDate=movie_initialReleaseDate+i.get_text()
#片長: 142分鐘
movie_Runtime=movie_info_items.find("span",{"property":"v:runtime"}).get_text()
#又名: 月黑高飛(港) / 刺激1995(臺) / 地獄諾言 / 鐵窗歲月 / 消香克的救贖
movie_alias=None
#IMDb鏈接: tt0111161
movie_IMDb_url=movie_info_items.find("a",{"rel":"nofollow"}).attrs["href"]
#電影評分信息
movie_votes_info=bsobj.find("div",{"id":"interest_sectl"})
#處理電影評分信息
#評分
movie_vote=movie_votes_info.find("strong",{"class":"ll rating_num"}).get_text()
#評分人數(shù)
movie_voters=movie_votes_info.find("span",{"span":"v:votes"})
#5星數(shù)量
movie_vote_5_stars=movie_votes_info.find_all("span",{"class":"rating_per"})[0].get_text()
print("正在解析電影信息",movie_id)
except:
print("電影信息解析失敗")
return None
return [movie_id,
movie_name,
movie_vote,
movie_voters,
movie_vote_5_stars,
movie_directer,
movie_attrs,
movie_actors,
movie_genre,
movie_saition,
movie_language,
movie_initialReleaseDate,
movie_Runtime,
movie_alias,
movie_IMDb_url,
movie_intro,
movie_photos]
# 獲取海報(bào)頁面前30張圖片的地址
def get_img_url(movie_url):
html=urlopen(movie_url)
bsobj=BeautifulSoup(html, features="html.parser")
img_url=bsobj.find_all("div",{"class":"cover"})
l=[]
for i in img_url:
url=i.find("img").attrs["src"]
l.append(url)
#print("-"*20)
print("30張海報(bào)地址獲取成功!")
return l
# 下載海報(bào)
def download_img(url,download_directory):
"""
保存海報(bào)
輸入:下載文件地址腿箩,和板存路徑
輸出:將文件保存到相應(yīng)文件下
"""
if not os.path.exists(download_directory):
os.makedirs(download_directory)
file_path=download_directory+url[-14:]
try:
urlretrieve(url, file_path)
#print("1")
print("下載圖片:%s完成豪直!" % (url[-14:]))
time.sleep(1)
except :
print("下載圖片:%s失敗珠移!" % (url[-14:]))
return None
#處理信息
def save_infos(infos,Directory):
filepath = Directory + "/info/"
if not os.path.exists(filepath):
os.makedirs(filepath)
conn = mysql.connector.connect(user='root', password='password', database='douban250')
cursor = conn.cursor()
print("-"*20)
print("正在將movie%s信息存到數(shù)據(jù)庫顶伞。。剑梳。唆貌。"%(infos[0]))
cursor.execute('insert into movie_250_info \
(\
movie_id,\
movie_name,\
movie_vote,\
movie_voters,\
movie_vote_5_stars,\
movie_directer,\
movie_attrs,\
movie_actors,\
movie_genre,\
movie_saition,\
movie_language,\
movie_initialReleaseDate,\
movie_Runtime,\
movie_alias,\
movie_IMDb_url,\
movie_intro,\
movie_photos\
) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)', infos)
conn.commit()
conn.close()
print("正在將文件存儲為txt。垢乙。锨咙。。追逮。")
with open(filepath+ infos[0][3:] + ".txt", "a") as fi:
for i in infos:
if type(i) == list:
for x in i:
fi.write(x)
fi.write("\t")
continue
fi.write(i if i is not None else "None")
fi.write("\n")
print("存儲完成酪刀!")
print("-" * 20)
#下載圖片
def save_img(infos,Directory):
img = get_img_url(infos[-1])
filepath=Directory + "/pic/" + infos[0] + "/"
print("-" * 20)
print("海報(bào)儲存在",filepath)
#print("-" * 20)
#print("\n")
for i in img:
download_img(i, filepath)
print("海報(bào)30張下載成功")
print("-" * 20)
def main():
Directory = "C:/Users/葛蘇健/Desktop/py/python小案例/004爬取豆瓣新片"
print("正在獲取10頁目錄。钮孵。骂倘。。巴席。")
ten_pageUrl = get_ten_pageurl()
print("正在連接數(shù)據(jù)庫历涝。。漾唉。荧库。。")
conn = mysql.connector.connect(user='root', password='password', database='douban250')
cursor = conn.cursor()
cursor.execute('create table movie_250url '
'(id int primary key, '
'name varchar(20), '
'url char(50))')
cursor.execute('create table movie_250_info (\
movie_id char(10) primary key,\
movie_name text,\
movie_vote char(20),\
movie_voters char(20),\
movie_vote_5_stars char(20),\
movie_directer text,\
movie_attrs text,\
movie_actors text,\
movie_genre text,\
movie_saition text NULL,\
movie_language text NULL,\
movie_initialReleaseDate text,\
movie_Runtime text,\
movie_alias text NULL,\
movie_IMDb_url text,\
movie_intro text,\
movie_photos text\
)')
conn.commit()
time.sleep(1)
print("連接成功赵刑!")
print("正在獲取250個(gè)電影的詳情頁url分衫。。般此。蚪战。")
movieInfoPage_queue = get_250movie_page_url(ten_pageUrl, Directory)
print("獲取250個(gè)電影的詳情頁url成功牵现!")
time.sleep(1)
# movieInfoPage_queue =MyQueue()
# movieInfoPage_queue.destroy()
# movieInfoPage_queue.push("https://movie.douban.com/subject/1291546/")
# # #info_path = Directory + "/info.txt"
print("正在創(chuàng)建文件夾")
if not os.path.exists(Directory):
os.makedirs(Directory)
print("創(chuàng)建成功")
print("開始爬取top250電影:")
print("#"*50)
while not movieInfoPage_queue.isEmpty():
try:
print("=" * 50)
print("正在獲取頁面。邀桑。施籍。")
info = get_movie_info(movieInfoPage_queue.top())
movieInfoPage_queue.pop()
print("開始解析電影詳情頁。概漱。丑慎。")
infos = configure_infopage(info)
print("解析電影詳情頁成功!")
save_infos(infos,Directory)
print("開始下載海報(bào):")
save_img(infos,Directory)
print("海報(bào)下載完成\n")
print("電影%s爬取成功瓤摧!"%(infos[0]))
print("=" * 50)
print("\n")
except:
#print("=" * 50)
print("失敻土选!")
print("=" * 50)
print("\n")
continue
if __name__ == '__main__':
main()
可以做做修改照弥,提高效率腻异,然后將存數(shù)據(jù)庫和提取信息的地方的容錯(cuò)率提高一點(diǎn),這樣每一頁就都能保存下來了这揣。下周尋個(gè)有點(diǎn)難度但是內(nèi)容不要太多悔常。
加油!最近在復(fù)習(xí)計(jì)算機(jī)網(wǎng)絡(luò)——tcp/ip——應(yīng)用層->運(yùn)輸層->網(wǎng)絡(luò)層->數(shù)據(jù)鏈路層->物理層给赞,這里的應(yīng)用層和運(yùn)輸層之間還是不太理解机打。
。片迅。残邀。。柑蛇。
2019年3月17日17:04:44