Part1:需求簡要描述
1、抓取http://www.jokeji.cn網站的笑話
2、以瀑布流方式顯示
Part2:安裝爬蟲框架Scrapy1.4
1、 安裝Scrapy1.4
E:\django\myProject001>pip install scrapy
執行報錯:
error: Unable to find vcvarsall.bat
Failed building wheel for Twisted
2、安裝wheel
E:\django\myProject001>pip install wheel
3、下載編譯好的wheel文件
訪問下面鏈接下載編譯好的wheel文件到當前目錄下
https://www.lfd.uci.edu/~gohlke/pythonlibs/
4、安裝編譯好的wheel文件
E:\django\myProject001>pip install Twisted-17.9.0-cp35-cp35m-win_amd64.whl
E:\django\myProject001>pip install Scrapy-1.4.0-py2.py3-none-any.whl
5、查看Scrapy是否安裝成功
E:\django\myProject001>scrapy version
Scrapy 1.4.0
6、安裝Py32Win模塊
E:\django\myProject001>pip install pypiwin32
訪問windows系統API的庫
7、安裝OpenPyXL
E:\django\myProject001>pip install openpyxl
用於將爬取數據寫入Excel文件
Part3:創建項目及應用
1、創建項目及應用
E:\django>django-admin startproject myProject001
E:\django>cd myProject001
E:\django\myProject001>python3 manage.py startapp joke
2、修改settings.py
文件路徑:myProject001\myProject001\settings.py
# 增加應用 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'joke', ] # 修改amind管理后台語言 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai'
3、修改modles.py
文件路徑:myProject001\joke\models.py
from django.db import models class Jokes(models.Model): jokeText = models.TextField(u'笑話內容') createDate = models.DateField(u'創建日期', auto_now_add=True) modifyDate = models.DateField(u'修改日期', auto_now=True)
4、創建數據遷移文件並執行
E:\django\myProject001>python3 manage.py makemigrations
E:\django\myProject001>python3 manage.py migrate
使用SQLite查看數據庫,表創建成功
5、修改views.py
文件路徑:myProject001\joke\views.py
from django.shortcuts import render from django.http import HttpResponse def index(request): return HttpResponse('這里是笑話應用的首頁')
6、在joke應用下創建urls.py
文件路徑:myProject001\joke\urls.py
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ]
7、修改項目應用下的urls.py
文件路徑:myProject001\myProject001\urls.py
from django.contrib import admin from django.urls import path,include urlpatterns = [ path('admin/', admin.site.urls), path('joke/', include('joke.urls')), ]
8、創建應用首頁模板文件index.html
模板文件路徑:
myProject001\joke\templates\joke\index.html
模板文件內容:
<html> <head> <title>笑話應用的首頁</title> </head> <body> </body> </html>
9、啟動應用
E:\django\myProject001>python3 manage.py runserver
訪問如下地址,應用創建成功
http://127.0.0.1:8000/joke/
Part4:了解XPath一些基本知識
1、節點和屬性
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <title>開心一刻</title> <link rel="icon" href="/favicon.ico" type="image/x-icon" /> <link href="css/list.css" rel="stylesheet" type="text/css" /> </head> <body> <div class="style_top"> <div class="list_title"> <ul> <li> <a href="/jokehtml/bxnn/2017122722221351.htm"target="_blank" >醉人的笑容你會有</a> </li> <li> <a href="/jokehtml/fq/201712272221462.htm"target="_blank" >搞笑夫妻樂事兒多</a> </li> <li> <a href="/jokehtml/mj/2017122722205011.htm"target="_blank" >幽默密切聯系生活</a> <i> </ul> </div> </div> </body> </html>
節點/元素:html、head、body、div、li 等
節點/元素文本內容:開心一刻、醉人的笑容你會有
屬性:class、href 等
屬性值:style_top、/jokehtml/bxnn/2017122722221351.htm 等
2、XPath使用路徑表達式選取節點
表達式 | 描述 | 實例 |
節點名稱 | 選取此節點的所有子節點 | body |
/ | 從根節點選取 | /html |
// | 選擇文檔中的節點,而不考慮位置 | //li |
. | 選取當前節點 | .//title |
.. | 選取當前節點的父節點 | |
@ | 選取屬性 | //@href |
謂語 | 找某個特定的節點或者包含某個指定的值的節點 | //title[@lang='eng'] |
* | 任意元素 | //* |
@* | 任意屬性 | //title[@*] |
node() | 任意類型 | |
| | 或運算符 | //title | //price |
: | 命名空間 | my:* |
text() | 文本內容 | /html/head/title/text() |
response.xpath() | 返回選擇器列表,使用xpath語法選擇的節點 | response.xpath('//base/@href').extract() |
response.css() | 返回選擇器列表,使用css語法選擇的節點 | response.css('base::attr(href)').extract() |
response.extract() | 返回被選擇元素的unicode字符串 | |
response.re() | 返回通過正則表達式提取的unicode字符串列表 |
Part5:分析網頁源代碼確定抓取數據的邏輯
1、笑話內容頁面源碼分析
笑話內容所在的html代碼
<span id="text110"> <P>1、為了省腮紅錢,我每天出門給自己兩個耳光。</P> <P>2、不要把今天的工作拖到明天,明天還不是要做?還不如干脆點,今天就把工作辭了。 </P> <P>3、朋友,你聽我一句勸,錢沒了可以再掙,所以我找你借的那筆錢就不還了吧。</P> <P>4、正能量的東西也不能多看,就好比自己挺窮的,哪能天天看有錢人的生活?肯定越看越傷心。還不如多看點更喪的東西,顯得自己元氣尚存。</P> <P>5、根據一個人的車,我們就能看出這個人是什么樣的。比如:如果它在溝里,它就是女人的車。</P> <P>6、以前小時候女鬼總喜歡在夢里嚇我,現在長大了,懂事了,單身久了,女鬼都不敢出現了!</P> <P>7、我喜歡了一個女生,為了弄清楚她是什么樣的人,所以我關注她小號。然后被她發現,扇了我一巴掌,把我從廁所趕出來了。</P> <P>8、老是看到有人說趴在蘭博基尼方向盤上哭,然后大家都很羡慕的樣子,所以我想問一下,哪里有蘭博基尼方向盤出售?</P> <P>9、這個世界上漂亮女孩已經那么多,為啥不能多一個我?</P> <P>10、我都19了,還沒來月經,身邊的女孩紙胸都老高了,我還是平胸,怎么辦啊!可怕的是腿上胳膊上汗毛老長了,更更可怕的是褲襠里,長出來個可怕的東西,有時候軟軟的,有時候硬硬的,好可怕啊,我該怎么辦?</P> </span>
翻頁所在的html代碼
<div class=zw_page1> 下一篇:<a href="../../JokeHtml/bxnn/2017122722221351.htm">爆逗二貨,醉人的笑容你會有</a> </div> <div class=zw_page2> 上一篇:<a href="../../JokeHtml/bxnn/2017122900222852.htm">搞笑很出色的是二貨</a> </div>
2、定義提取邏輯
先依據初始鏈接提取笑話內容
分支1:
提取下一篇鏈接,依據下一篇鏈接提取笑話內容
如此循環,直至沒有下一篇鏈接
分支2:
提取上一篇鏈接,依據上一篇鏈接提取笑話內容
如此循環,直至沒有上一篇鏈接
Part6:創建Scrapy項目抓取數據
1、創建Scrapy項目
E:\scrapy>scrapy startproject myScrapy1815
執行上面的命令生成項目myScrapy1815
再在目錄myScrapy1815\myScrapy1815\spiders\下創建文件myJoke_spider.py
項目的完整目錄結構如下
2、定義Item
Item是保存爬取到的數據的容器,可以理解為編程中的對象。一個Item即一個對象保存的是一條記錄。
打開文件myScrapy1815\myScrapy1815\items.py
# -*- coding: utf-8 -*- # Define here the models for your scraped items # # See documentation in: # http://doc.scrapy.org/en/latest/topics/items.html import scrapy class Myscrapy1815Item(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() pass class JokeItem(scrapy.Item): # 正文內容 joke_content = scrapy.Field()
3、編寫Spider
打開文件myScrapy1815\myScrapy1815\spiders\myJoke_spider.py
添加如下內容
import scrapy from scrapy.http.request import Request from myScrapy1815.items import JokeItem class JokeSpider(scrapy.Spider): name = "joke" allowed_domains = ["jokeji.cn"] start_urls = [ "http://www.jokeji.cn/JokeHtml/bxnn/2017122900211092.htm" ] def parse(self, response): # 獲取笑話內容 jokes = response.xpath('//span[@id="text110"]/p').extract() for joke in jokes: item = JokeItem() item['joke_content'] = joke yield item # 獲取下一篇鏈接 nexthref = response.xpath('//div[@class="zw_page1"]/a/@href').extract_first() if nexthref is not None: # 將相對url轉為絕對url nexthref = response.urljoin(nexthref) # 繼續獲取下一篇笑話 yield Request(nexthref, callback=self.parseNexthref) # 獲取上一篇鏈接 prevhref = response.xpath('//div[@class="zw_page2"]/a/@href').extract_first() if prevhref is not None: # 將相對url轉為絕對url prevhref = response.urljoin(prevhref) # 繼續獲取下一篇笑話 yield Request(prevhref, callback=self.parsePrevhref) def parseNexthref(self, response): # 獲取笑話內容 jokes = response.xpath('//span[@id="text110"]/p').extract() print(jokes) for joke in jokes: item = JokeItem() item['joke_content'] = joke yield item # 獲取下一篇鏈接 nexthref = response.xpath('//div[@class="zw_page1"]/a/@href').extract_first() if nexthref is not None: # 將相對url轉為絕對url nexthref = response.urljoin(nexthref) # 繼續獲取下一篇笑話,測試時可以將下一行代碼注釋掉 #yield Request(nexthref, callback=self.parseNexthref) def parsePrevhref(self, response): # 獲取笑話內容 jokes = response.xpath('//span[@id="text110"]/p').extract() for joke in jokes: item = JokeItem() item['joke_content'] = joke yield item # 獲取上一篇鏈接 prevhref = response.xpath('//div[@class="zw_page2"]/a/@href').extract_first() if prevhref is not None: # 將相對url轉為絕對url prevhref = response.urljoin(prevhref) # 繼續獲取上一篇笑話,測試時可以將下一行代碼注釋掉 #yield Request(prevhref, callback=self.parsePrevhref)
4、編寫Item Pipeline
當Item在Spider中被收集之后,它將會被傳遞到Item Pipeline
打開文件myScrapy1815\myScrapy1815\pipelines.py
# -*- coding: utf-8 -*- # Define your item pipelines here # # Don't forget to add your pipeline to the ITEM_PIPELINES setting # See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html import sqlite3 import json import re from openpyxl import Workbook class Myscrapy1815Pipeline(object): def __init__(self): # 文件文件 self.file = open('myItems.json', 'w', encoding='utf-8') # Excel文件 self.wb = Workbook() self.ws = self.wb.active self.ws.title = "笑話集" #定義sheet名稱 self.ws.append(['joke_content']) #定義表頭 # 數據庫連接 self.conn = sqlite3.connect("E:\\django\\myProject001\\db.sqlite3") # 當spider被開啟時方法被調用 def open_spider(self, spider): pass # 每個item pipeline組件都需要調用該方法 def process_item(self, item, spider): # 寫入文本文件 line = json.dumps(dict(item), ensure_ascii=False) + "\n" self.file.write(line) # 寫入Excel文件 self.ws.append([item['joke_content']]) self.wb.save('myItems.xlsx') # 寫入數據庫 record = item['joke_content'] record = record.replace('<BR>','\r\n') pattern = re.compile(r'<[^>]+>', re.S) record = pattern.sub('', record) sql="insert into joke_jokes(jokeText,createDate,modifyDate) values('"+record+"',datetime('now','localtime'),datetime('now','localtime'))" self.conn.execute(sql) self.conn.commit() return item # 當spider被關閉時方法被調用 def close_spider(self, spider): self.file.close() self.conn.close() pass
5、激活Item Pipeline
打開文件myScrapy1815\myScrapy1815\settings.py
刪掉如下三行代碼之前的注釋符#
ITEM_PIPELINES = { 'myScrapy1815.pipelines.Myscrapy1815Pipeline': 300, }
6、啟動Spider
E:\scrapy\myScrapy1815>scrapy crawl joke
抓取的數據,文本文件格式如下
抓取的數據,Excel文件格式如下
抓取的數據,保存在SQLite數據庫中如下
Part7:以瀑布流方式顯示笑話內容
1、修改settings.py
文件位置:myProject001\myProject001\settings.py
ALLOWED_HOSTS = ['10.61.226.236','127.0.0.1','localhost']
允許通過以上3個地址訪問
2、修改應用的urls.py
文件位置:myProject001\joke\urls.py
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), path('index_ajax/', views.index_ajax), ]
3、修改views.py
文件位置:myProject001\joke\views.py
from json import dumps from django.core import serializers from django.shortcuts import render from django.http import HttpResponse,JsonResponse from . import models # 頁面首次加載的記錄數 FIRST_PAGE_SIZE = 50 # Ajax每次加載的記錄數 PAGE_SIZE = 20 def index(request): # 首次加載 jokes = models.Jokes.objects.all()[0:FIRST_PAGE_SIZE] return render(request, 'joke/index.html', {'jokes':jokes}) def index_ajax(request): # 當前頁碼 pageIndex = int(request.POST.get('pageIndex','1')) # 總記錄數 totalCount = models.Jokes.objects.filter().count() # 定義提取記錄的范圍(數組上標、數組下標) lBound = FIRST_PAGE_SIZE + (pageIndex-1) * PAGE_SIZE uBound = FIRST_PAGE_SIZE + pageIndex * PAGE_SIZE # 是否有下一頁(上標小於總記錄數時有下一頁) hasNextPage = 1 if uBound <= totalCount else 0 # 如果上標大於或等於總記錄數,則上標使用總記錄數 if uBound >= totalCount: uBound = totalCount # 按范圍提取記錄 jokes = models.Jokes.objects.all()[lBound:uBound] # 序列化JSON json_data = {} json_data['hasNextPage'] = hasNextPage json_data['jokes'] = serializers.serialize('json', jokes) # 返回JSON格式的數據 return HttpResponse(JsonResponse(json_data), content_type="application/json")
4、修改模板文件index.html
文件位置:myProject001\joke\templates\joke\index.html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3個meta標簽*必須*放在最前面,任何其他內容都*必須*跟隨其后! --> <title>段子</title> {% load static %} <link rel="stylesheet" type="text/css" href="{% static 'joke/css/bootstrap.min_v3.3.7.css' %}" /> <link rel="stylesheet" type="text/css" href="{% static 'joke/css/style.css' %}" /> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script> <![endif]--> </head> <body> <div class="container"> <div id="masonry" class="row masonry"> {% for joke in jokes %} <div class="col-md-4 col-sm-6 col-xs-12 item "> <div class="well well-sm" style="line-height:180%;"> <span class="label label-success">{{ joke.pk }}</span> {{ joke.jokeText }} </div> </div> {% endfor %} </div> </div> <script src="{% static 'joke/js/jquery.min_v1.12.4.js' %}"></script> <script src="{% static 'joke/js/bootstrap.min_v3.3.7.js' %}"></script> <script src="{% static 'joke/js/masonry.pkgd.min_v4.2.0.js' %}"></script> <script src="{% static 'joke/js/imagesloaded.pkgd.min_v4.1.3.js' %}"></script> <!-- 瀑布流布局 --> <script> $("#loadingModal").modal('show'); $('.masonry').masonry({ //itemSelector: '.item' }); </script> <!-- 頁面無限加載 --> <script> // 默認加載第2頁 var pageIndex = 1; // 是否正在加載標記 var isLoading = 0; // 是否已提示沒有更多內容 var isNotice = 0; // 是否還有更多 var hasNextPage = 1; // 頁面滾動到底部,觸發加裝 $(window).scroll(function(){ var scrollTop = $(this).scrollTop(); var scrollHeight = $(document).height(); var windowHeight = $(this).height(); if(scrollTop + windowHeight == scrollHeight){ if(hasNextPage == 0 & isNotice == 0){ // 沒有下一頁內容時提示 isNotice = 1; $('#masonry').append('<div id="noticeInfomation" class="col-md-4 col-sm-6 col-xs-12 item "><div class="alert alert-warning"><strong>提示:</strong>沒有更多內容了...</div></div>'); $('#masonry').masonry('reloadItems'); $('#masonry').masonry('layout'); }else if(hasNextPage == 1 & isLoading == 0){ // 避免Ajax執行過程中反復被調用 isLoading = 1; $('#masonry').append('<div id="noticeInfomation" class="col-md-4 col-sm-6 col-xs-12 item "><div class="alert alert-warning"><strong>提示:</strong>正在加載更多內容...</div></div>'); $('#masonry').masonry('reloadItems'); $('#masonry').masonry('layout'); setTimeout("loadJoke(pageIndex);", 1000); } } }); // Ajax方法 function loadJoke(arg){ //var host = 'localhost'; //var port = '8000'; var labelCss = 'label label-info'; if(pageIndex%2 == 1){ labelCss = 'label label-info'; } else{ labelCss = 'label label-success'; } $.ajax({ url: "./index_ajax/", type: "POST", dataType: 'json', data: {pageIndex: arg}, success: function (data) { $("#noticeInfomation").remove(); hasNextPage = data['hasNextPage']; jokes = JSON.parse(data['jokes']); for (var obj in jokes){ $('#masonry').append('<div class="col-md-4 col-sm-6 col-xs-12 item "><div class="well well-sm" style="line-height:180%;">'+'<span class="'+labelCss+'">'+jokes[obj].pk+'</span> '+jokes[obj].fields.jokeText+'</div></div>'); $('#masonry').masonry('reloadItems'); $('#masonry').masonry('layout'); } pageIndex = pageIndex + 1; isLoading = 0; } }); }; //Django的CSRF保護機制(ajax) $.ajaxSetup({ data: {csrfmiddlewaretoken: '{{ csrf_token }}' }, }); </script> </body> </html>
5、啟動應用
訪問應用:http://10.61.226.236/joke/
頁面效果如下
6、補充說明
UI使用了bootstrap、jquery、masonry、ajax無限加載
=====結束=====