使用python+BeautifulSoup完成爬蟲抓取特定資料的工作,並使用Django搭建一個管理平台,用來協調抓取工作。
因為自己很喜歡Django admin後台,所以這次用這個後台對抓取到的連結進行管理,使我的爬蟲可以應對各種後期的需求。比如分時段抓取,週期性對已經抓取的地址重新抓取。資料庫是用python內建的sqlite3,所以很方便。
這幾天正好在做一個電影推薦系統,需要些電影資料。本文的例子是對豆瓣電影抓取特定的資料。
第一步:建立Django模型
模仿nutch的爬蟲思路,這裡簡化了。每次抓取任務開始先從資料庫裡找到未儲存的(is_save = False)的連結,放到抓取鏈表裡。你也可以根據自己的需求去過濾連結。
python代碼:
class Crawl_URL(models.Model):<br /> url = models.URLField('抓取地址',max_length=100, unique=True)<br /> weight = models.SmallIntegerField('抓取深度',default = 0)#抓取深度起始1<br /> is_save = models.BooleanField('是否已儲存',default= False)#<br /> date = models.DateTimeField('儲存時間',auto_now_add=True,blank=True,null=True)<br /> def __unicode__(self):<br /> return self.url
然後產生相應的表。
還需要一個admin管理後台
class Crawl_URLAdmin(admin.ModelAdmin):<br /> list_display = ('url','weight','is_save','date',)<br /> ordering = ('-id',)<br /> list_filter = ('is_save','weight','date',)<br /> fields = ('url','weight','is_save',)<br />admin.site.register(Crawl_URL, Crawl_URLAdmin)
第二步,編寫爬蟲代碼
爬蟲是單線程,並且每次抓取後都有相應的暫訂,豆瓣網會禁止一定強度抓取的爬蟲
爬蟲根據深度來控制,每次都是先產生連結,然後抓取,並解析出更多的連結,最後將抓取過的連結is_save=true,並把新連結存入資料庫中。每次一個深度抓取完後都需要花比較長的時候把連結匯入資料庫。因為需要判斷連結是否已存入資料庫。
這個只對滿足Regex http://movie.douban.com/subject/(/d+)/ 的地址進行資料解析。並且直接忽略掉不是電影模組的連結。
第一次抓取需要在後台加個連結,比如http://movie.douban.com/chart,這是個熱門排行榜的頁面,電影比較受歡迎。
python代碼:
#這段代碼不能格式化發
# coding=UTF-8
import urllib2
from BeautifulSoup import *
from urlparse import urljoin
from pysqlite2 import dbapi2 as sqlite
from movie.models import *
from django.contrib.auth.models import User
from time import sleep
image_path = 'C:/Users/soul/djcodetest/picture/'
user = User.objects.get(id=1)
def crawl(depth=10):
for i in range(1,depth):
print '開始抓取 for %d....'%i
pages = Crawl_URL.objects.filter(is_save=False)
newurls={}
for crawl_page in pages:
page = crawl_page.url
try:
c=urllib2.urlopen(page)
except:
continue
try:
#解析中繼資料和url
soup=BeautifulSoup(c.read())
#解析電影頁面
if re.search(r'^http://movie.douban.com/subject/(/d+)/$',page):
read_html(soup)
#解析出有效連結,放入newurls
links=soup('a')
for link in links:
if 'href' in dict(link.attrs):
url=urljoin(page,link['href'])
if url.find("'")!=-1: continue
if len(url) > 60: continue
url=url.split('#')[0] # removie location portion
if re.search(r'^http://movie.douban.com', url):
newurls[url]= crawl_page.weight + 1 #串連有效。存入字典中
try:
print 'add url :'
except:
pass
except Exception.args:
try:
print "Could not parse : %s" % args
except:
pass
#newurls存入資料庫 is_save=False weight=i
crawl_page.is_save = True
crawl_page.save()
#休眠2.5秒
sleep(2.5)
save_url(newurls)
#儲存url,放到資料庫裡
def save_url(newurls):
for (url,weight) in newurls.items():
url = Crawl_URL(url=url,weight=weight)
try:
url.save()
except:
try:
print 'url重複:'
except:
pass
return True
第三步,用BeautifulSoup解析頁面
抽取齣電影標題,圖片,劇情介紹,主演,標籤,地區。關於BeautifulSoup的使用可以看這裡BeautifulSoup技術文檔
#抓取資料<br />def read_html(soup):<br /> #解析出標題<br /> html_title = soup.html.head.title.string<br /> title = html_title[:len(html_title)-5]<br /> #解析齣電影介紹<br /> try:<br /> intro = soup.find('span',attrs={'class':'all hidden'}).text<br /> except:<br /> try:<br /> node = soup.find('div',attrs={'class':'blank20'}).previousSibling<br /> intro = node.contents[0]+node.contents[2]<br /> except:<br /> try:<br /> contents = soup.find('div',attrs={'class':'blank20'}).previousSibling.previousSibling.text<br /> intro = contents[:len(contents)-22]<br /> except:<br /> intro = u'暫無'</p><p> #取得圖片<br /> html_image = soup('a',href=re.compile('douban.com/lpic'))[0]['href']<br /> data = urllib2.urlopen(html_image).read()<br /> image = '201003/'+html_image[html_image.rfind('/')+1:]<br /> f = file(image_path+image,'wb')<br /> f.write(data)<br /> f.close()</p><p> #解析出地區<br /> try:<br /> soup_obmo = soup.find('div',attrs={'class':'obmo'}).findAll('span')<br /> html_area = soup_obmo[0].nextSibling.split('/')<br /> area = html_area[0].lstrip()<br /> except:<br /> area = ''</p><p> #time = soup_obmo[1].nextSibling.split(' ')[1]<br /> #time = time.strptime(html_time,'%Y-%m-%d')</p><p> #產生電影對象<br /> new_movie = Movie(title=title,intro=intro,area=area,version='暫無',upload_user=user,image=image)<br /> new_movie.save()<br /> try:<br /> actors = soup.find('div',attrs={'id':'info'}).findAll('span')[5].nextSibling.nextSibling.string.split(' ')[0]<br /> actors_list = Actor.objects.filter(name = actors)<br /> if len(actors_list) == 1:<br /> actor = actors_list[0]<br /> new_movie.actors.add(actor)<br /> else:<br /> actor = Actor(name=actors)<br /> actor.save()<br /> new_movie.actors.add(actor)<br /> except:<br /> pass</p><p> #tag<br /> tags = soup.find('div',attrs={'class':'blank20'}).findAll('a')<br /> for tag_html in tags:<br /> tag_str = tag_html.string<br /> if len(tag_str) > 4:<br /> continue<br /> tag_list = Tag.objects.filter(name = tag_str)<br /> if len(tag_list) == 1:<br /> tag = tag_list[0]</p><p> new_movie.tags.add(tag)<br /> else:<br /> tag = Tag(name=tag_str)<br /> tag.save()<br /> new_movie.tags.add(tag)<br /> #try:</p><p> #except Exception.args:<br /> # print "Could not download : %s" % args<br /> print r'download success'<br />
豆瓣的電影頁面並不是很對稱,所以有時候抓取的結果可能會有點出入
抓取到的電影