var regex0=new RegExp("(i?)(\<img)([^\>]+\>)", "gmi") //正則匹配表達式
this.newcontent=this.content.replace(regex0,"$2 style='display:block;margin: auto;width:120px;' $3")
//下面這個則需要在$2 $3左右添加和修改東西
個正則表達式就是匹配所有的img標簽//踩坑完畢,可以直接使用
第二行代碼按自己需要改改~
var r=$("#detail").html().replace(regex0,"[圖片]");
原文鏈接:https://blog.csdn.net/qq_59747594/article/details/124822379
在爬取網頁時,對網頁數據清洗時常會遇到空格,有的網頁空格類型還不止一種,如果不能正確處理,可能無法提取到需要的數據。這里記錄下自己使用正則處理各種類型空格的經歷。
這里把空格格式分兩類,一類這里表述為普通文本空格,另一類表述為html實體空格。普通文本空格介紹 普通半角空格 和 普通全角空格 。html實體空格介紹三種,分別為 html實體不間斷空格 ( )、 html實體半角空格 ( )和 html實體全角空格 ( )。
這種空格不需要特殊處理,使用正則匹配,可以直接使用空格或者\s。為了以下鋪墊,這里也舉個用unicode碼匹配該類型空格的例子,代碼如下所示
s='hello word, hi python'
print re.findall(r'i py', s) # 直接用空格
print re.findall(r'i\spy', s) # 用\s
print re.findall(ur'i\u0020py', s) # 用unicode碼
執行結果如下
該鏈接 導航欄各欄目之間有空格(這個就是\u3000類型的,但是直接看不出來,代碼抓取下來可以看到),如下圖所示
以下使用代碼獲取該段文本,并使用正則提取
import re
from requests import get
from lxml import etree
url='http://hebng.hljcourt.gov.cn/public/detail.php?id=1818'
headers={
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0'}
resp=get(url, headers=headers)
html=resp.content.decode('gbk')
et=etree.HTML(html)
text_list=et.xpath('/html/body/table[3]/tr[2]//text()')
text=et.xpath('string(/html/body/table[3]/tr[2])')
print '=' * 50
print re.findall(ur'法院概況 新聞中心', text) # 匹配不到
print re.findall(ur'法院概況\s新聞中心', text) # 匹配不到
print re.findall(ur'法院概況\u3000新聞中心', text) # 這樣才可以匹配到
print '=' * 50
執行結果如下
從以上圖片可以看到該網頁導航欄各欄目之間的空格就是這種\u3000這種空格,這種類型空格要匹配的話需要在正則表達式中使用unicode碼。
該鏈接 正文之間有很多不間斷空格,打開開發者工具可以直接看到
以下使用代碼獲取該段文本,并使用正則提取
import re
from requests import get
from lxml import etree
url='http://sthj.tj.gov.cn/ZWGK4828/ZFXXGK8438/FDZDGK27/XZCFQZXZCFXX7581/202010/t20201020_3958760.html'
headers={
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0'}
resp=get(url, headers=headers)
html=resp.content.decode('utf-8')
et=etree.HTML(html)
text_list=et.xpath('//*[@id="zoom"]/p[30]/text()')
text=et.xpath('string(//*[@id="zoom"]/p[30])') # 注意申請行政復議前面有四個空格,其中三個不間斷空格,一個普通半角空格
print '=' * 200
print re.findall(ur'\s\s申請行政復議', text) # 普通半角接普通半角匹配不到
print re.findall(ur'\xa0\s申請行政復議', text) # 不間斷空格接普通半角空格可以匹配到
print re.findall(ur'\u00A0\s申請行政復議', text) # 不間斷空格接普通半角空格可以匹配到
print '=' * 200
執行結果如下
從以上圖片結果可以看出,使用正則匹配非間斷空格時,需要使用unicode碼\u00A0或者十六進制\xa0。
該鏈接 正文末尾的日期前面有很多**&ensp**這種空格,打開開發者工具可以直接看到
以下使用代碼獲取該段文本,并使用正則提取
import re
from requests import get
from lxml import etree
url='http://sthj.tj.gov.cn/ZWGK4828/ZFXXGK8438/FDZDGK27/XZCFQZXZCFXX7581/202112/t20211207_5743296.html'
headers={
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0'}
resp=get(url, headers=headers)
html=resp.content.decode('utf-8')
et=etree.HTML(html)
text_list=et.xpath('//*[@id="zoom"]/div/p[38]/span[1]/text()')
text=et.xpath('string(//*[@id="zoom"]/div/p[38]/span[1])')
print '=' * 200
print re.findall(ur'\s20', text) # 普通半角匹配不到
print re.findall(ur'\u200220', text) # unicode碼\u2002可以匹配到
print '=' * 200
執行結果如下
從以上圖片結果可以看出,使用正則匹配html實體半角空格時,需要使用unicode碼\u2002。
該鏈接 正文表格表頭有一列中有該類型空格, ,如下圖所示
以下使用代碼獲取該段文本,并使用正則提取
import re
from requests import get
from lxml import etree
url='http://hebng.hljcourt.gov.cn/public/detail.php?id=1818'
headers={
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0'}
resp=get(url, headers=headers)
html=resp.content.decode('gbk')
et=etree.HTML(html)
text_list=et.xpath('//table[@class="ke-zeroborder"]/tbody/tr[1]/td[1]/text()')
text=et.xpath('string(table[@class="ke-zeroborder"]/tbody/tr[1]/td[1])')
print '=' * 200
print re.findall(ur'\t\s\r', text_list[0]) # 普通半角匹配不到
print re.findall(ur'\t\u2003\r', text_list[0], flags=re.S) # unicode碼\u2003可以匹配到
print '=' * 200
執行結果如下
從以上結果看出,使用正則匹配html實體半角空格時,需要使用unicode碼\u2003。
從以上幾個例子可以看出,網頁上的空格類型要想處理好,是要兼顧幾種情況的,其實爬蟲主要遇到的就是\xa0、\u3000這兩種??梢允褂媒y一正則匹配,如下測試代碼
import re
s=u'\u2002\u2003\xa0\u3000Say'
print len(s)
print s
print re.findall(r'\s{4}Say', s) # 普通空格匹配不到
print re.findall(ur'[\u2002\u2003\xa0\u3000]{4}Say', s) # 使用unicode碼可以匹配到
print re.findall(r'\s{4}Say', s, flags=re.U) # 使用re.U模式可以匹配到
代碼執行結果如下
注意看以上結果,這里的s是unicode字符串,共7個字符,其中四個不同類型的空格,使用對應的unicode碼可以匹配到這些空格。
要注意下當正則模式的編譯標志位(flags)為re.U時,使用正則符號\s是可以匹配到各種類型的空格的 。
最后安利一個查unicode字符的網站unicode-table,可以在 html實體 這里看到有許多html中不同類型的空格。
歡迎大家關注我的微信公眾號“IT工匠”獲取更多資源(涉及算法、數據結構、java、深度學習、計算機網絡、python、Android等互聯網技術資料)。
筆者之前的寫作習慣一直是在本地(Mac+Typora+Ipac)寫好之后將markdown代碼粘貼到csdn,圖片是Ipac自動上傳到微博匿名圖床上,用了大概一年多都沒有問題,直到前段時間突然發現我csdn文章里面的圖片無法加載了,就像下面這樣:
本來以為是微博圖床掛了,結果發現圖片的鏈接還是可以正常訪問的,本地Typora上也是可以正常顯示圖片的,問了一下csdn的工作人員,說是微博圖床加了防盜鏈,所以現在csdn不能自動加載了,真是又氣又無奈,沒辦法,誰讓自己當初貪圖小便宜用了免費圖床了,既然問題已經出了就要想辦法解決,首先是訂閱了Ipac,這樣可以支持自定義圖床(默認的Ipac只能支持微博匿名圖床),筆者選擇的是阿里云Oss,有免費額度,個人圖床夠用。但是這樣只能保證我之后寫的文章不會因為圖床的導致圖片掛掉,那之前的怎么辦….如果可以將之前文章里面的圖片從圖床上下載下來,然后傳到我新的圖床上,然后再將原文的圖片鏈接由原來的圖床鏈接替換為現在新的圖片鏈接就可以完美解決了啊,但是由于文章太多,一篇一篇手動操作實在是太慢,既然是程序員,就應該用代碼解決,所以有了本文,本文的主要思路如下圖所示:
我們首先打開csdn的登陸頁面,這里我們選擇賬號密碼登陸,方便提取信息:
image-20190518200820418
我們隨便個賬號和密碼,看看點擊登陸之后該站點會做什么:
我們發現,這里執行了一個doLogin,見名知意,這個應該就是真正的登陸的請求,我們點開看看詳情:
重點在于我用紅圈圈出來的那里,將我們輸入的用戶名和密碼傳進去,然后發起登陸請求,所以,我們只需要模擬這個doLogin就可以了,代碼如下:
def doLogin(userId, password): """ 模擬登陸,獲取cookie以及username :param userId: :param password: :return: """ url="https://passport.csdn.net/v1/register/pc/login/doLogin" payload="{\"loginType\":\"1\",\"pwdOrVerifyCode\":\"" + password + "\",\"userIdentification\":\"" + userId + "\",..." headers={ 'accept': "application/json, text/plain, */*", 'accept-encoding': "gzip, deflate, br", 'accept-language': "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7", 'content-type': "application/json;charset=UTF-8", 'origin': "https://passport.csdn.net", 'referer': "https://passport.csdn.net/login", 'user-agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36", 'x-requested-with': "XMLHttpRequest", 'Cache-Control': "no-cache", 'Postman-Token': "407dbd4f-90ba-494c-994f-2de739d73a96,b506b3e0-b247-40af-b52b-2622c2148687", 'Host': "passport.csdn.net", 'Connection': "keep-alive", 'cache-control': "no-cache" } session=requests.session() response=session.request("POST", url, data=payload.encode('utf-8'), headers=headers) jsonObject=json.loads(bytes.decode(response.content)) if jsonObject['message']=='success': print('登錄:{userId}成功'.format(userId=userId)) return session, jsonObject else: print('登錄:{userId}失敗:'.format(userId=userId) + jsonObject['message']) return None
這樣就完成了模擬登陸,注意這里返回的是一個session和 jsonObject,session是requests中的概念,返回session就一個目的,利用登陸成功后的cookie,這樣才能在后面修改你的文章, jsonObject是登陸成功后csdn服務端給我們返回的信息,這里將其jsonObject返回的目的是獲取當前userId對應的username(userId指的是你在csdn利用賬號密碼登錄時輸入的那個用戶名,一般是郵箱或者手機號碼,username是csdn給你分配的一個標識)。
這里爬取所有文章id相當于獲取了當前作者的所有文章列表,我們先看看指定作者的文章列表頁:
可以看到url是:https://blog.csdn.net/username/article/list/index,username就是剛才我們登錄時返回的那個,index是頁面的序號,因為大家基本都是很多頁文章,所以index從0往上增加,我們看看這個頁面的html代碼:
可以很容易地發現文章列表的位置,分析了一下發現每一篇文章都有一個data-articleid,這就是我們需要的文章id啊,所以思路就是模擬請求https://blog.csdn.net/username/article/list/index,拿到返回的html后使用正則表達式匹配data-articleid即可,注意這里有個細節就是,在我畫紅括弧的緊鄰上一個< div >標簽,有一個style="display:none;"的元素,這個不是我們需要的,但是他也有data-articleid屬性,所以我們在使用正則表達式匹配到當前html頁面的所有data-articleid屬性后應該忽略第一條,代碼如下:
def getArticleIdList(userId, maxListPage=100): """ 獲取指定userId用戶的所有文章的iD :param userId: :param maxListPage: :return: """ articleList=[] count=0 for index in range(maxListPage): url='https://blog.csdn.net/' + userId + '/article/list/' + str(index) requestParm={"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) ...", "Accept-Language": "en-US,en;q=0.5"} response=requests.get(url, params=requestParm) if response.status_code==200: pattern=re.compile(r"data-articleid=\".*?\"") resultList=pattern.finditer(bytes.decode(response.content)) flag=0 for result in resultList: if flag==0: flag=flag + 1 continue print('正在獲取第{count}條文章Id'.format(count=count)) flag=flag + 1 count=count + 1 item=re.search("\".*?\"", result.group()) articleList.append(item.group().strip('\"')) if flag==0: break else: break print('共獲取到{count}條文章id'.format(count=len(articleList))) return articleList
獲取到文章的id列表之后我們就可以爬取文章了,我們爬取文章的目的是獲取到當前文章的markdown或者是html源代碼,然后在本地做圖片鏈接的替換,那么我們肯定要去文章的編輯頁面找規律,而不是在文章的詳情頁面,因為詳情頁面大概率只會返回html,不會返回markdown源代碼,我們隨便找一篇文章,點擊”編輯”,進入編輯頁面:
我們刷新一下頁面:
可以看到有個很顯眼的getAriticle,我們看看其response:
可以發現這個getAriticle返回了當前文章的html代碼、markdown代碼等文章信息,需要的參數就是文章id,所以我們只需要模擬這個getAriticle請求即可,代碼如下:
def getArticle(articleId, session): """ 獲取文章源碼 :param articleId: :param session: :return: """ url='https://mp.csdn.net/mdeditor/getArticle?id=' + articleId requestParams={ 'id': articleId } headers={'accept-encoding': 'gzip, deflate, br', 'accept-language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7', 'referer': 'https://mp.csdn.net/mdeditor/90272525', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36' } result=session.get(url, params=requestParams, headers=headers) jsonObject=json.loads(bytes.decode(result.content)) if jsonObject['status']==True: print('獲取' + articleId + '內容成功') return jsonObject['data'] else: print('獲取' + articleId + '內容失敗:' + jsonObject['error']) return None
獲取到文章的html和markdown代碼之后要做的就是使用正則表達式匹配圖片鏈接了,由于筆者對正則表達式不是很熟悉,所以只能很笨拙地使用,歡迎大家提出改進,這里直接給出代碼:
def lambdaToGetMarkdownPicturePosition(content): """ 從markdownd代碼中提取圖片鏈接 :param content: :return: """ pattern=re.compile(r"!\[.*?\]\(http.*?\)") resultList=pattern.finditer(content) urlList=[] for item in resultList: curStr=item.group() curStr=curStr.split('(')[1] curStr=curStr.strip(')') urlList.append(curStr) print(curStr) return urlList ''' <img alt="" class="has" src="http://www.jungjaehyung.com/uploadfile/2024/1009/20241009010104854.jpg" /> ''' def lambdaToGetHtmlPicturePosition(content): """ 從html代碼中提取圖片鏈接 :param content: :return: """ pattern=re.compile(r"<img.*?>") resultList=pattern.finditer(content) urlList=[] for item in resultList: searchObject=re.search(r'src=".*?"', item.group()) curStr=searchObject.group() curStr=curStr.split('"')[1] curStr=curStr.strip('"') urlList.append(curStr) return urlList
這個就根據每個人選擇的圖床不一樣做法也不一樣,但是思路是一樣的,我使用的是阿里oss服務,使用其提供的sdk可以很方便地將圖片從原來的鏈接遷移到現有的圖床:
def putUrlPicToAliOss(url, pictureName): """ 將圖片遷移到阿里oss存儲 :param url: :param pictureName: :return: """ if baseUrl in url: return None global oosSession if oosSession is None: oosSession=requests.session() # requests.get返回的是一個可迭代對象(Iterable),此時Python SDK會通過Chunked Encoding方式上傳。 input=oosSession.get(url) result=bucket.put_object(pictureName, input) resultUrl=baseUrl + pictureName if result.status==200: return resultUrl else: return None
這部分直接使用python中str的replace即可,核心代碼很簡單:
markdowncontent=markdowncontent.replace(mdUrl, resultUrl) content=content.replace(htmlUrl, resultUrl)
替換好圖片鏈接后最重要的一步就是將修改后的鏈接保存到csdn的服務器,這里還是從csdn的文章編輯界面找信息:
發現當我們點擊發表文章之后有一個saveArticle,分析其請求體之后可以肯定這個saveArticle就是用來保存文章的,由于請求體內容過多,這里就不貼原圖了,大家可以在自己的chrome上看一下,我們只需要模擬這個saveArticle即可,代碼如下:
def saveArticle(jsonObject, session): """ 保存文章到csdn的服務器 :param jsonObject: :param session: """ boundary='------WebKitFormBoundary7MA4YWxkTrZu0gW' id=jsonObject['id'] title=jsonObject['title'].strip() articleedittype=jsonObject['articleedittype'] description=jsonObject['description'] content=jsonObject['content'] markdowncontent=jsonObject['markdowncontent'] if markdowncontent is not None: markdowncontent.strip() # private=jsonObject['private'] private='1' tags=jsonObject['tags'] categories=jsonObject['categories'].replace(' ', '') channel=jsonObject['channel'] type=jsonObject['type'] # type='original' status=jsonObject['status'] read_need_vip=jsonObject['read_need_vip'] url="https://mp.csdn.net/mdeditor/saveArticle" payload="{boundary}\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n" \ "{title}\r\n" \ "{boundary}\r\nContent-Disposition: form-data; name=\"markdowncontent\"\r\n\r\n " \ "{markdowncontent}\r\n" \ "{boundary}\r\nContent-Disposition: form-data; name=\"content\"\r\n\r\n" \ "{content}\r\n" \ "{boundary}\r\nContent-Disposition: form-data; name=\"id\"\r\n\r\n " \ "{id}\r\n" \ "{boundary}\r\nContent-Disposition: form-data; name=\"private\"\r\n\r\n" \ "\r\n" \ "{boundary}\r\nContent-Disposition: form-data; name=\"read_need_vip\"\r\n\r\n " \ "{read_need_vip}\r\n" \ "{boundary}\r\nContent-Disposition: form-data; name=\"tags\"\r\n\r\n " \ "{tags}\r\n" \ "{boundary}\r\nContent-Disposition: form-data; name=\"status\"\r\n\r\n " \ "{status}\r\n" \ "{boundary}\r\nContent-Disposition: form-data; name=\"categories\"\r\n\r\n " \ "{categories}\r\n" \ "{boundary}\r\nContent-Disposition: form-data; name=\"channel\"\r\n\r\n " \ "{channel}\r\n" \ "{boundary}\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\n" \ "{type}\r\n" \ "{boundary}\r\nContent-Disposition: form-data; name=\"articleedittype\"\r\n\r\n " \ "{articleedittype}\r\n" \ "{boundary}\r\nContent-Disposition: form-data; name=\"Description\"\r\n\r\n " \ "{description}\r\n" \ "{boundary}\r\nContent-Disposition: form-data; " \ "name=\"csrf_token\"\r\n\r\n\r\n{boundary}-- ".format(boundary=boundary, title=title, id=id,markdowncontent=markdowncontent,content=content, private=private,tags=tags, status=status, categories=categories,channel=channel, read_need_vip=read_need_vip,articleedittype=articleedittype,description=description, type=type) headers={ 'accept': "*/*", 'accept-encoding': "gzip, deflate, br", 'accept-language': "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7", 'content-type': "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW", 'origin': "https://mp.csdn.net", 'referer': "https://mp.csdn.net/mdeditor/90292004", 'user-agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36", 'Cache-Control': "no-cache", 'Postman-Token': "76c8f028-258f-462e-9cd4-00294e3e620d,d3e13008-b401-4d1d-ac5e-d8cb1b13901f", 'Host': "mp.csdn.net", 'Connection': "keep-alive", 'cache-control': "no-cache" } response=session.request("POST", url, data=payload.encode('utf-8'), headers=headers) jsonObject=json.loads(bytes.decode(response.content)) if jsonObject['status']==True: print('保存' + id + '內容成功') else: print('保存' + id + '內容失敗' + response.content)
主要做法就是將getArticle返回的內容只改變markdowncontent和content屬性,然后進行保存操作。
完整代碼:關注微信公眾號“IT工匠”,后臺回復“PCsdn”獲取。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。