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这两种。可以使用统一正则匹配,如下测试代码
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.hmttv.cn/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小时内与您取得联系。