頁數據提取的方法很多,從其基本原理來說很多就是通過模擬http請求,發送給服務器,然后接收響應,解析響應的結果。整個過程說簡單也簡單,說復雜也復雜。這里來整理下做過的一些事,走過的路,遇到的坑。
1,基本思路
這里舉一個java下載的例子,說明簡單的思路。
public void downPDF(String urlString, String filename, String pdf,
String chk, String chk1, String chk2, String chk3) throws Exception {
URL server=new URL(urlString);
HttpURLConnection connection=(HttpURLConnection) server
.openConnection();
connection.setRequestMethod("GET");
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setUseCaches(false);
connection.addRequestProperty("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
connection.addRequestProperty("Accept-Language", "zh-cn,zh;q=0.5");
connection.addRequestProperty("Accept-Encoding", "gzip, deflate");
connection.addRequestProperty("Accept-Charset","GB2312,utf-8;q=0.7,*;q=0.7");
connection.addRequestProperty("Cookie","chk=");
connection.addRequestProperty("User-Agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR2.0.50727; MS-RTC LM 8)");
// if (headers !=null) {
// for (Header h : headers) {
// System.out.println(h.getName() + ":" + h.getValue());
// connection.addRequestProperty(h.getName(), h.getValue());
// }
// }
connection.connect();
InputStream is=connection.getInputStream();
OutputStream os=new FileOutputStream(filename);
byte[] buffer=new byte[1024 * 128];
if (true) {
int byteReaded=is.read(buffer);
while (byteReaded !=-1) {
os.write(buffer, 0, byteReaded);
byteReaded=is.read(buffer);
}
爬蟲正則表達式參考我上一篇文章:Python 爬蟲正則表達式和re庫
在爬蟲過程中,可以利用正則表達式去提取信息,但是有些人覺得比較麻煩。因為花大量時間分析正則表達式。這時候可以用高效的網頁解析庫Beautiful Soup。
Beautiful Soup 是一個HTML/XML 的解析器,主要用于解析和提取 HTML/XML 數據。
Beautiful Soup支持Python標準庫中的HTML解析器,還支持一些第三方的解析器,如果我們不安裝它,則 Python 會使用 Python默認的解析器,lxml 解析器更加強大,速度更快,推薦安裝。
下面是各種解析器優缺點
Beautiful Soup 3 目前已經停止開發,推薦在現在的項目中使用Beautiful Soup 4,不過它已經被移植到BS4了,也就是說導入時我們需要 import bs4。
安裝Beautiful Soup
pip install beautifulsoup4
根據操作系統不同,可以選擇下列方法來安裝lxml,安裝解析器:
apt-get install Python-lxml
easy_install lxml
pip install lxml
創建對象時,指定解析器,這里為lxml
from bs4 import BeautifulSoup
bs=BeautifulSoup(html,"lxml")
Beautiful Soup將復雜HTML文檔轉換成一個復雜的樹形結構,每個節點都是Python對象,所有對象可以歸納為4種: Tag , NavigableString , BeautifulSoup , Comment .
(1)Tag
標簽,最基本的信息組織單元,分別用<>和標明開頭和結尾,通俗點講就是 HTML 中的一個個標簽。
Tag有很多方法和屬性,tag中最重要的屬性: name和attributes。
name:
每個tag都有自己的名字,通過 .name 來獲取:
慣例,同樣以豆瓣電影排行做分析,鏈接為:https://movie.douban.com/top250
import requests
from bs4 import BeautifulSoup
headers={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/65.0.3325.162 Safari/537.36'}
url='https://movie.douban.com/top250'
req=requests.get(url,headers=headers)
html=req.text
#print(req.text)
soup=BeautifulSoup(html,'lxml')
print(soup.h1)
print(soup.a)
執行結果:
<h1>豆瓣電影 Top 250</h1>
<a class="nav-login" href="https://accounts.douban.com/passport/login?source=movie" rel="nofollow">登錄/注冊</a>
以上,就直接提取到標簽h1和a 的內容了,之所以只有一個,因為只提取第一個匹配到的內容。
Attributes:
屬性,一個tag可能有很多個屬性, . tag的屬性的操作方法與字典相同。
如上:ol class="grid_view" 的屬性, 標簽名為ol,屬性為class,屬性值為:grid_view
import requests
from bs4 import BeautifulSoup
headers={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/65.0.3325.162 Safari/537.36'}
url='https://movie.douban.com/top250'
req=requests.get(url,headers=headers)
html=req.text
#print(req.text)
soup=BeautifulSoup(html,'lxml')
#獲取標簽div所有屬性,得到的是一個字典
print(soup.div.attrs)
#獲取標簽ol屬性為class的值
print(soup.ol['class'])
執行結果:
{'id': 'db-global-nav', 'class': ['global-nav']}
['grid_view']
因為是字典屬性,所以tag的屬性可以被添加,刪除或修改。不過,對于修改刪除的操作,不是我們的主要用途,有需要的自行參考官方文檔。
(2)NavigableString
直譯為:可以遍歷的字符串,通過名稱可知,得到字符串。
標簽內非屬性字符串,格式:soup.\<tag>.string, NavigableString可以跨越多個層次。
如,得到了標簽的內容,要想獲取標簽內部的文字,用 .string 即可。
上面代碼改為:
print(soup.h1.string)
執行結果:
豆瓣電影 Top 250
(3)BeautifulSoup
BeautifulSoup 對象表示的是一個文檔的全部內容.大部分時候,可以把它當作 Tag 對象,是一個特殊的 Tag,我們可以分別獲取它的類型,名稱。
print(soup.name)
執行結果:
[document]
(4)Comment
注釋及特殊字符串,Tag , NavigableString , BeautifulSoup 幾乎覆蓋了html和xml中的所有內容,但是還有一些特殊對象.容易讓人擔心的內容是文檔的注釋部分:
from bs4 import BeautifulSoup
markup="<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup=BeautifulSoup(markup)
comment=soup.b.string
print(type(comment))
print(comment)
執行結果:
<class 'bs4.element.Comment'>
Hey, buddy. Want to buy a used parser?
HTML基本格式:<>…</>構成了所屬關系,遍歷形成了標簽的樹形結構。
所以有時候不能做到一步就得到想要的元素,需要先選中一個元素再以它為基準再選擇它的子節點,父節點,兄弟節點等。
(1)子節點和子孫節點
子節點屬性:.contents .children
.content
tag 的 .content 屬性可以將tag的子節點以列表的方式輸出
import requests
from bs4 import BeautifulSoup
headers={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/65.0.3325.162 Safari/537.36'}
url='https://movie.douban.com/top250'
req=requests.get(url,headers=headers)
html=req.text
#print(req.text)
soup=BeautifulSoup(html,'lxml')
print(soup.ol.contents)
執行結果:
['\n', <li>
<div class="item">
<div class="pic">
<em class="">1</em>
<a href="https://movie.douban.com/subject/1292052/">
<img alt="肖申克的救贖" class="" src="https://img9.doubanio.com/view/photo/s_ratio_poster/public/p480747492.jpg" width="100"/>
</a>
</div>
<div class="info">
...
輸出方式為列表,所以可以用列表索引來獲取它的某一個元素。
print(soup.ol.contents[1])
.children
返回的不是一個 list,不過我們可以通過遍歷獲取所有子節點。
print(soup.ol.children)
執行結果:
<list_iterator object at 0x7fbf14fbc4c0>
打印輸出 .children ,可以發現它是一個 list 生成器對象,所以需要遍歷一下獲取內容。
for child in soup.ol.children:
print(child)
輸入內容和.content差不多。
子孫節點.descendants
如果要獲得所有的子孫節點的話,可以調用descendants屬性,返回結果還是生成器,所以需要遍歷一下輸出可以看見span節點
print(soup.ol.descendants)
#print(soup.ol.children)
for child in soup.ol.descendants:
print(child)
descendants會遞歸查詢所有子節點,得到所有的子孫節點。
(2)父節點和祖父節點
獲取父節點.parent
print(soup.ol.parent)
獲取所有祖先節點,同理需要遍歷獲取。
for parent in soup.ol.parents:
print(parent)
(3)兄弟節點
獲取兄弟節點:.next_sibling 和 .previous_sibling
next_sibling和previous_sibling分別獲取節點的下一個和上一個兄弟元素。
print(soup.li.next_sibling)
print(soup.li.previous_sibling)
如果節點不存在,則返回 None,實際中通常是字符串或空白,因為空白或者換行也可以被視作一個節點,所以得到的結果可能是空白或者換行。
全部兄弟節點:next_siblings 和 previous_siblings
分別返回后面和前面的兄弟節點,同理,所有節點需要遍歷獲得。
print(soup.li.next_siblings)
print(soup.li.previous_siblings)
for sibling in soup.li.next_siblings:
print(sibling)
for previous in soup.li.previous_siblings:
print(previous)
(4)回退和前進節點
前后節點:.next_element 和 .previous_element
與 .next_sibling .previous_sibling 不同,它并不是針對于兄弟節點,而是在所有節點,不分層次
比如 head 節點為
<head><title>The Dormouse's story</title></head>
那么它的下一個節點便是 title,它是不分層次關系的。
所有前后節點:.next_elements 和 .previous_elements
同理,返回的是迭代器,需要遍歷獲得。
eautiful Soup定義了很多搜索方法,主要用的2個方法:find() 和 find_all()
(1)find_all
語法:find_all(name, attrs, recursive, text, **kwargs)
name:
我們可以根據節點名來查詢元素。name可以是:字符串、正則表達式、列表、True、方法
print(soup.find_all('a'))
因為是Tag類型,我們可以進行嵌套查詢.
for a in soup.find_all('a'):
print(a.find_all('span'))
print(a.string)
attrs
除了根據節點名查詢的話,同樣的也可以通過屬性來查詢。
print(soup.find_all(attrs={'id': 'link1'}))
print(soup.find_all(attrs={'name': 'Dormouse'}))
常用的屬性比如class,我們可以直接傳入class這個參數。在這里需要注意的是class是Python的保留字,所以在class的后面加上下劃線。
print(soup.find_all(class_="title"))
執行結果:
<span class="title">肖申克的救贖</span>, <span class="title"> / The Shawshank Redemption</span>, <span class="title">霸王別姬</span>, <span class="title">阿甘正傳</span>
(2)find
除了find_all( )方法,還有find( )方法,前者返回的是多個元素,以列表形式返回,后綴是返回一個元素。即第一個元素。
find( )與find_all( )的使用方法相同。
find_parents() 和find_parent():前者返回所有祖先節點,后者返回直接父節點。
find_next_siblings()和find_next_sibling():前者返回后面的所有兄弟節點,后者返回后面第一個兄弟節點。
find_previous_siblings和find_previous_sibling():前者返回前面的所有兄弟節點,后者返回前面第一個兄弟節點。
Beautiful Soup還提供了另一種選擇器,即CSS選擇器。
soup.select(),返回類型是 list。
同樣可以用 標簽名、類名、 id 名、組合、屬性查找。
(1)soup.select()
獲取title標簽節點
print(soup.select('title'))
獲取class為title的節點
print(soup.select('.title'))
獲取li標簽下的a節點
print(soup.select('li a'))
查找時還可以加入屬性元素,屬性需要用中括號括起來,
注意屬性和標簽屬于同一節點,所以中間不能加空格,否則會無法匹配到
print soup.select('a[href="http://example.com/elsie"]')
(2)嵌套選擇
同樣可以使用嵌套查詢
for ul in soup.select('ul'):
print(ul.select('li'))
(3)獲取屬性
for ul in soup.select('ul'):
print(ul['id'])
print(ul.attrs['id'])
(4)獲取文本
for li in soup.select('li'):
print('String:', li.string)
print('get text:', li.get_text())
通過以上的方法,現在獲取豆瓣電影排行首頁的排名、電影名、導演演員、年份類型。
從頁面分析,所有影片信息,在class標簽值為grid_view的里面。
所以第一步獲取所有grid_view里面所有li標簽的值,返回的是一個列表。
list=soup.find(class_='grid_view').find_all('li')
排序,在列表每個元素中,獲取em標簽值,為排序,只取字符串。
find('em').string
電影名稱,獲取第一個title值
find(class_='title').string
導演和年代信息,在標簽p當中,獲取的是text文本格式。由于中間有空格,還有br換行符,所以最后還需要replace替換掉。
item.find('p').text.replace(' ','')
最終代碼為:
url='https://movie.douban.com/top250'
req=requests.get(url,headers=headers)
html=req.text
#print(req.text)
soup=BeautifulSoup(html,'lxml')
list=soup.find(class_='grid_view').find_all('li')
for item in list:
item_num=item.find('em').string
item_name=item.find(class_='title').string
item_act=item.find('p').text.replace(' ','')
print("排名:"+item_num,"\n電影名稱:"+item_name,item_act)
執行結果:
排名:1
電影名稱:肖申克的救贖
導演:弗蘭克·德拉邦特FrankDarabont 主演:蒂姆·羅賓斯TimRobbins/...
1994 / 美國 / 犯罪劇情
排名:2
電影名稱:霸王別姬
導演:陳凱歌KaigeChen 主演:張國榮LeslieCheung/張豐毅FengyiZha...
1993 / 中國大陸中國香港 / 劇情愛情同性
排名:3
電影名稱:阿甘正傳
導演:羅伯特·澤米吉斯RobertZemeckis 主演:湯姆·漢克斯TomHanks/...
1994 / 美國 / 劇情愛情
排名:4
電影名稱:泰坦尼克號
導演:詹姆斯·卡梅隆JamesCameron 主演:萊昂納多·迪卡普里奧Leonardo...
1997 / 美國墨西哥澳大利亞加拿大 / 劇情愛情災難
排名:5
電影名稱:這個殺手不太冷
導演:呂克·貝松LucBesson 主演:讓·雷諾JeanReno/娜塔莉·波特曼...
1994 / 法國美國 / 劇情動作犯罪
排名:6
電影名稱:美麗人生
導演:羅伯托·貝尼尼RobertoBenigni 主演:羅伯托·貝尼尼RobertoBeni...
1997 / 意大利 / 劇情喜劇愛情戰爭
排名:7
電影名稱:千與千尋
導演:宮崎駿HayaoMiyazaki 主演:柊瑠美RumiH?ragi/入野自由Miy...
2001 / 日本 / 劇情動畫奇幻
排名:8
電影名稱:辛德勒的名單
導演:史蒂文·斯皮爾伯格StevenSpielberg 主演:連姆·尼森LiamNeeson...
1993 / 美國 / 劇情歷史戰爭
排名:9
電影名稱:盜夢空間
導演:克里斯托弗·諾蘭ChristopherNolan 主演:萊昂納多·迪卡普里奧Le...
2010 / 美國英國 / 劇情科幻懸疑冒險
排名:10
電影名稱:星際穿越
導演:克里斯托弗·諾蘭ChristopherNolan 主演:馬修·麥康納MatthewMc...
2014 / 美國英國加拿大 / 劇情科幻冒險
排名:11
電影名稱:忠犬八公的故事
導演:萊塞·霍爾斯道姆LasseHallstr?m 主演:理查·基爾RichardGer...
2009 / 美國英國 / 劇情
排名:12
電影名稱:楚門的世界
導演:彼得·威爾PeterWeir 主演:金·凱瑞JimCarrey/勞拉·琳妮Lau...
1998 / 美國 / 劇情科幻
排名:13
電影名稱:海上鋼琴師
導演:朱塞佩·托納多雷GiuseppeTornatore 主演:蒂姆·羅斯TimRoth/...
1998 / 意大利 / 劇情音樂
排名:14
電影名稱:三傻大鬧寶萊塢
導演:拉庫馬·希拉尼RajkumarHirani 主演:阿米爾·汗AamirKhan/卡...
2009 / 印度 / 劇情喜劇愛情歌舞
排名:15
電影名稱:機器人總動員
導演:安德魯·斯坦頓AndrewStanton 主演:本·貝爾特BenBurtt/艾麗...
2008 / 美國 / 科幻動畫冒險
排名:16
電影名稱:放牛班的春天
導演:克里斯托夫·巴拉蒂ChristopheBarratier 主演:讓-巴蒂斯特·莫尼...
2004 / 法國瑞士德國 / 劇情喜劇音樂
排名:17
電影名稱:無間道
導演:劉偉強/麥兆輝 主演:劉德華/梁朝偉/黃秋生
2002 / 中國香港 / 劇情犯罪驚悚
排名:18
電影名稱:瘋狂動物城
導演:拜倫·霍華德ByronHoward/瑞奇·摩爾RichMoore 主演:金妮弗·...
2016 / 美國 / 喜劇動畫冒險
排名:19
電影名稱:大話西游之大圣娶親
導演:劉鎮偉JeffreyLau 主演:周星馳StephenChow/吳孟達ManTatNg...
1995 / 中國香港中國大陸 / 喜劇愛情奇幻古裝
排名:20
電影名稱:熔爐
導演:黃東赫Dong-hyukHwang 主演:孔侑YooGong/鄭有美Yu-miJung/...
2011 / 韓國 / 劇情
排名:21
電影名稱:控方證人
導演:比利·懷爾德BillyWilder 主演:泰隆·鮑華TyronePower/瑪琳·...
1957 / 美國 / 劇情犯罪懸疑
排名:22
電影名稱:教父
導演:弗朗西斯·福特·科波拉FrancisFordCoppola 主演:馬龍·白蘭度M...
1972 / 美國 / 劇情犯罪
排名:23
電影名稱:當幸福來敲門
導演:加布里爾·穆奇諾GabrieleMuccino 主演:威爾·史密斯WillSmith...
2006 / 美國 / 劇情傳記家庭
排名:24
電影名稱:觸不可及
導演:奧利維·那卡什OlivierNakache/艾力克·托蘭達EricToledano 主...
2011 / 法國 / 劇情喜劇
排名:25
電影名稱:怦然心動
導演:羅伯·萊納RobReiner 主演:瑪德琳·卡羅爾MadelineCarroll/卡...
2010 / 美國 / 劇情喜劇愛情
推薦使用lxml解析庫,必要時選擇html.parser。相對于正則表達式,Beautiful Soup更加簡單,但是網上有些推薦正則表達式,理由是精確。
具體用哪個,還是根據環境選擇吧,一起使用都可以。
看下效果圖
1、給目標div增加鼠標按下事件,記錄下div對象的位置(left,top)和鼠標點擊的位置(x,y)
2、監聽鼠標移動事件,在移動過程中計算出鼠標的移動位置(nx,xy),然后實時計算出鼠標從點擊到現在的偏移量,然后再將div的位置修改,這樣就實現了div位置的變動
ps:目標div的屬性position要設置absolute或者relative
3、div可以移動了,但什么時候停止移動呢?當然是鼠標彈起的時候,所以我們監聽鼠標彈起的事件,并結束移動
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>div-drag-每天一個知識點</title>
<style>
.dragable {
width: 200px;
height: 200px;
border: 1px solid darkorchid;
position: relative;
}
</style>
</head>
<body>
<div class="dragable"></div>
</body>
<script>
var dragDiv=document.querySelector(".dragable");
dragDiv.addEventListener("mousedown", function(e) {
//獲取div初始位置對象
var divRect=dragDiv.getBoundingClientRect();
//獲取鼠標點擊的位置
var downX=e.clientX;
var downY=e.clientY;
//開關打開
var startMove=true;
//設置樣式為移動光標
dragDiv.style.cursor="move";
window.onmousemove=function(e) {
if (!startMove) {
return;
}
let newLeft=e.clientX - downX + divRect.left;
let newTop=e.clientY - downY + divRect.top;
dragDiv.style.left=newLeft + "px";
dragDiv.style.top=newTop + "px";
return false;
};
window.onmouseup=function(e) {
//結束移動
startMove=false;
dragDiv.style.cursor="default";
return false;
};
e.stopPropagation();
});
</script>
</html>
代碼手敲可運行
當然這個實現還有很多可擴展或者不足,比如怎么限制div移動的范圍,能力有限,在以后的日子里會嘗試編寫js組件,實現更完善的功能。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。