针对萌新思路分享:基于urllib.request的威客平台订单发现推送爬虫(一)
本帖最后由 roshinntou 于 2021-3-15 22:54 编辑感谢论坛313注册开放日,有机会加入论坛跟大家一起学习交流。 正如标题,我想把自己写爬虫的思路跟大家分享一下。我不是程序员出身,相关内容也是靠自学,所以想把自己的一些心得跟大家,主要是针对那些对爬虫一知半解还处于听说过python爬虫,刚刚部署好环境,正在复制别人代码摸索探索的朋友一起分享交流,共同进步。 文中也只会用到比较基础的urllib.request来实现各种爬取操作。主要分享的也是一些思路和解决方法,我觉得只要掌握了基本思路,就可以很快上手,未来使用类似Scrapy、PySpider、Crawley之类的框架也可以很快理解并掌握。
第二部分:python基础入门IO文件读写操作与邮件发送 主要说明获取信息之后如何对比出最新需求、对比出新需求后如何发邮件通知我们
预计还有:
第三部分:将最新需求保存进数据库,完整地修改代码,让代码可以定时重复执行,同时功能上可以获取多个分类的订单信息,向多人发送邮件。基于数据库用crm系统快速做出一个应用级后台。
第四部分:获得一个Linux云服务器,给服务器安装宝塔面板,将代码部署到服务器,后台运行。
正在写,之后会更新。
这次还会穿插讲解一些其他使用爬虫中可能会用到的技术:
1、 设定等待时间,防止反扒拒绝访问
2、 文件操作
3、 数据库操作
4、 邮件通知
5、 正则表达式、字符串的基本操作
6、 Lxml相关的XPath查找方法
项目背景:
朋友在国内开了一家小的设计公司,在威客平台上接单赚钱,但是苦于人手不够不能及时对新出现的订单进行投标,错过很多机会,想要我帮他写一个简单的爬虫在发现新需求后通知她。 目标网站: 一品威客(实际上这个事情是19年朋友跟我说的,他是在猪八戒平台,我当时给她做的也是猪八戒平台的爬虫,但是毕竟今天给大家介绍的是写爬虫的思路,猪八戒我也已经很熟悉了,就换一个新的平台,这样很多细节不会因为我已经知道而忽略,写这个文章的时候我只知道目标网站的名称,我会把我的整个思路都记录下来。)
那么开始今天的分享,最开始我们得明白如何达到“发现新需求之后通知我”这个目的。整个过程分为:1、 获得新需求所在页面的HTML代码2、 识别出新需求3、 将新需求的信息发送给我们
一、找到新需求的页面:首先,找到新需求发布的网页https://task.epwk.com/
可以看到这是一个带筛选的列表。
那么这个页面就可以作为我们采集信息的目标,当然实际项目中可以更进一步,根据分类选择“设计”之后,列表里出现的就都是需要设计的需求,路径是https://task.epwk.com/sj/
1、识别出新需求点击按照时间排序,之后地址会变为https://task.epwk.com/sj/?o=7,那么有经验的小伙伴就明白这个是表单数据的参数筛选,o传入不同值给出不同的排列结果。
当然说了这么多废话实想要大家多了解url地址结构,有时候我们会人为的构造一些url地址来实现我们的一些爬取需要。 观察了这个网址10分钟左右,发现有新的需求出现后,一定会出现在表单第一行,而原来第一行的会被排到第二行。
那么我们的目标就明确了: 多次刷新https://task.epwk.com/sj/?o=7这个网页,找到列表的第一个需求,然后判断这个需求是不是最新的,如果是则给我们发送通知。
但是现在又出现了新的问题:
a. 如何找到第一个需求?
b. 如何判断这个需求是不是最新的?
对于第一个问题,我们有很多解决的办法,我举两个最有代表性的方法:
a) 通过目标元素的xPath来寻找
b) 通过网页HTML代码的特征使用字符串处理来寻找
首先各位需要了解chrome内核浏览器和F12的相关知识,但是xPath不需要专业的知识,只需要简单的逻辑思考就可以正常使用了。
那么关于XPath获取数据的方法,第一步我们先找到第一个需求的标题A标签,复制它的xPath地址:
得到的结果是:
/html/body/div/div/div/div/div/div/h3/a
而第二个需求拼途纸箱设计的xPath地址是:
/html/body/div/div/div/div/div/div/h3/a
之后的依次是:
/html/body/div/div/div/div/div/div/h3/a
/html/body/div/div/div/div/div/div/h3/a
/html/body/div/div/div/div/div/div/h3/a
………
那么我们就可以发现规律,倒数第二个div后边的数字代表它是这个列表里的第几行数据,当然后边的数据咱们暂时用不到,不过未来有需要可以考虑更多的需求,比如遇到新需求时验证原来的第一个是不是被下放到第二行,以此来判断有没有漏过极短时间内出现的多个新需求。(小tips:如果我们想要一次性获取当前页的所有数据信息,只需要删除掉差异性的部分即可,也就是我们找到的倒数第二个DIV后边的””) 那么现在,我们可以使用lxml的etree来实现XPath信息的获取,可以得到类似:需求名称、预算、链接。但是获取的时候也是有所区别的。
我们的A标签完整内容是:
<a urlshare="urlshare" indus_name="如果你够牛,就来参加任务赚钱吧。任务内容:" indus_pname="模具公司企业logo设计" g_name="" desc="一.模具公司企业logo企业名称:埃科法工业;英文名称:ECHOFAST是一家以注塑、压铸模具制造..." url="https://task.epwk.com/923879/"title="模具公司企业logo设计" class="font14" target="_blank"> 模具公司企业logo设计</a>
<a>模具公司企业logo设计</a> 标签中间的文本信息,也就是黄色的文字可以使用:[.text]来获取
而我们看到了上边完整的a标签里还有desc、url、href、title这些属性,我们可以使用【.get('属性名称')】来获取对应的数据
比如:
想要获取网址可以使用 【.get(’href’)】来获取
想要获取订单标题使用 【.get(’title)】来获取
那么完整的代码就是:
from lxml import etree
import urllib.requestdef getHtmlCode(url):
#创建对象
req_one = urllib.request.Request(url)
#header信息
req_one.add_header('User-Agent', 'Mozilla/6.0')
#加入 try catch模块防止request报错导致结束程序
try:
#注意!timeout参数如果设置了,有可能出现XX秒内没有执行完毕报错
res_one = urllib.request.urlopen(req_one,timeout=1)
#内容重编码后复制给变量
htmlcode = res_one.read().decode('utf-8')
#关闭urlopen方法
res_one.close()
#把结果返回
return htmlcode
except:
print("报错IP问题无法获得")
return "error"if __name__ == '__main__':
#调用我们写的getHtmlCode()方法传入url获取html代码
code = getHtmlCode("https://task.epwk.com/sj/?o=7")
#构建eTree
html = etree.HTML(code)
#通过XPath获取标题所在<a>标签的属性
name = html.xpath('/html/body/div/div/div/div/div/div/h3/a')
#通过XPath获取预算所在标签的属性
price = html.xpath('/html/body/div/div/div/div/div/div/h3/b')
print('最新的需求是:')
#一个html标签之间的文本信息使用.text获取
print(name.text)
#a 标签的href属性也就是链接属性可以使用get('属性名')来获取
print(name.get('href'))
print(name.get('indus_name'))
print(price.text)
执行后运行的结果如图:
上面我们说了如何获取XPath,通过XPath来获取相关信息,但是也有可能出现:我们获取信息需要在不明确数量的网站的不同的页面里找相似的信息,不同的页面导致有可能html结构完全不一样,无法通过同一条XPath定位,比如我要获取页面的url链接,然后进入这些url链接里找到一些固定格式的情报,并继续获取他们页面里的URL链接持续的获取几层网页里的情报信息。就不能通过XPath来查找定位了。 那么还是以找第一个需求为例子
先找到这个需求所在的A标签,请注意看网页里标题部分被半透明的蓝色区域覆盖。这个蓝色区域是你鼠标在然后逐级向上移动鼠标,各位会发现随着你们不断的选择html标签,网页里的蓝色区域也会相应发生变化。
最后我选择到了列表所在的底层DIV,class是task_class_list_li如下图:
那么查看源代码,并搜索这个DIV,发现他下面下面不远就是最新的需求内容和预算了。
之后可以通过字符串查找、下标截取等方式获取我们需要的信息了。为了防止可能出现的错误,我们需要先找到不管出现任何情况下都可以把第一个需求的代码段获取到的方法。 这个需要我们看一下对方仿站的结构:
一个div里是全部的列表,然后每一行数据都是底层div里的一个小div名字是task_class_list_li_box,我们完全可以查找task_class_list_li_box,第一个和第二个之间的内容就是咱们最新的需求
我们使用字符串的index()方法来截取出第一个需求的所有HTML代码
先把名为task_class_list_li_box的div全部代码设为目标startTarget = '<divclass="task_class_list_li_box">'获取html代码,这个是我们刚刚创建的单独方法
code = getHtmlCode(url)
#先找到列表所在的DIV层,使用index方法 Code是完整的HTML,index方法传入目标字符串后会获得这个字符串第一个字符的下标数字。
下标数字是说,比如一个文档,第一个字或者符号排序是1,第二个字或者符号排序是2,这个1、2就是这个字或者符号的下标,通过下标我们可以快速找到一个文档内的具体的文字。
这里我需要找的字符串是:'<divclass="task_class_list_li_box">',返回的数字是这串字符串第一个字母符号”<”在文本中的位置,实际上我们要找的内容其实是在最后一个符号“>”之后开始的,所以起始的位置应该是符号“<”所在的位置数字+这串字符串的长度也就是len(startTarget)
taskStartIndex =code.index(startTarget)+len(startTarget)
好了,现在我们已经得到最新需求起始的位置了,接下来我们需要得到结束的位置,同样使用index() 方法:taskEndIndex =code.index(startTarget,taskStartIndex,len(code))
与刚刚的index()方法不同的是,这次我们给方法传入了三个参数,分别是:(需要查询的字符串,从哪个下标位置开始,从哪个下标位置结束)
code.index(startTarget,taskStartIndex,len(code))这里的参数startTarget是那个DIVbox的标签,我们用它来分割所有的需求。
第一个需求的结尾肯定是第二个需求开始的位置之前,所以这里我们还是搜索这个box DIV,然后起始的位置是我们刚刚确认的第一个box DIV的位置,最后的参数是整个字符串的结尾这段代码的意思就是:“从第一个BOX出现开始直到最后一个字符,找到第二个BOX出现的位置”
最后,我们获得了未处理的第一个需求相关的代码,在整个html网页代码里的起始、结束位置。
然后我们使用字符串截取来获得这段代码:
untreatedInfo =code
code 的意思是,获得一个新的字符串,它从code这个字符串的第a位开始,到第b位结束。
为了验证,我们执行一下会获得如下的结果
其中需求的预算、地址、名称就都出现在一段复杂的html代码里了。
那么重复刚刚的操作,比如我想获取预算,我们以预算前边的‘<b class=”red”>’为起始目标,</b>为结束目标截取中间的字符串就可以了。
以上这些是字符串处理的笨办法。
不过,如果想要获取需求的标题或者url,其实也有简单的办法来做的。这个就是正则表达式。
不清楚正则表达式的可以搜索一下相关教程。完全不懂也没关系的,有很多在线的正则表达式测试网站,可以动态调试正则表达式,只需要复制内容,然后多次测试编写正则表达式,就可以最后成功的写出需要的表达式。
下面我以一个初步了解正则表达式的新手知识水品,来尝试通过正则表达式获取网址链接和标题
首先找一个正则表达式在线测试网站各位可以在百度随便找。然后把刚刚的结果复制粘贴进来
在正则表达式那一栏里随便填写自己想要的表达式,下面的结果会动态出现,非常方便。
为了展示效果我填写了网址的一种表达式方法,已经有结果了,不过我们是初学者,一般会使用固定+动态混合的方式来查找。 比如这里,我们发现他们的订单地址构成是:https://task.epwk.com/一串六位数的数字/根据解释:
我写正则表达式的时候可以写成:“https://task.epwk.com/.......”或者“https://task.epwk.com/....../”
点代表了除了换行符以外的任意字符,前边域名是固定的后边是6位数字加一个反斜杠,那么就写成了上边的域名后边7个点或者6个点加反斜杠,结果就是下图这样的
我们成功的找到了网址。
当然,有可能未来订单数字变成7位,我们可以把表达式改成:https://task.epwk.com/\d*/把6个点改成 \d* \d代表数字,\d后边跟一个*也就是\d*代表无数个数字,最后跟一个反斜杠,那么中间不管多少位数字,都可以正常识别出来
然后就是标题了,我们发现标题都是在title里写的。我们直接查title=".*" 中间.*的意思是 .代表除了换行的所有字符*代表重复任意次,但是重复任意次会截取非常长的内容。这时我们需要告诉正则表达式,你给我截取到下一个双引号就停下来,不要这么贪婪,全都给我截取了。在.*后边加?
他就会乖乖的直接去到双引号结束。如果各位不了解的话,可以在上边随便多输入几次下方会自动匹配的,你可以在不断尝试中修改自己的正则表达式。
这里虽然结果里有很多其他的内容,但是根据我们的截取位置判断,不管什么情况,我们的标题都肯定是第一个结果,那么正则表达式截取之后只需要取第一个值就行。
剩下的就是代码的过程:
正则表达式需要引入re依赖包
import re
具体的方法是:
re.search('正则表达式',从哪个字符串里找)
写成我们的方法应该是:
taskUrl= re.search(‘https://task.epwk.com/\d*/’, untreatedInfo)
这个方法返回的是一个数组,因为刚刚大家也看到了我们用正则表达式匹配的时候有可能搜到很多相匹配的内容。但是我们刚刚也研究过了,不管是url地址还是标题,都是我们正则表达式匹配出来的第一个结果。所以完整的方法应该是:
taskUrl= re.search(‘https://task.epwk.com/\d*/’, untreatedInfo).group(0)taskTitle = re.search('title=".*?"',untreatedInfo).group(0)
后边添加了.group(0)方法,0就代表是第一个。如果你们未来自己使用的过程中发现是正则表达式匹配出来的第2个结果,括号里就是1,第三个结果,括号就是2
但是我们发现,这样匹配的标题还带有title=””这些东西,实际我们只需要中间的内容。有两种办法,第一个是我刚刚提到的截取字符串的功能,直接从第一个双引号后边开始,到这个字符串长度减一也就是第二个双引号之前就行。
或者,我们可以使用字符串的replace替换方法。
. replace(目标,结果)taskTitle = re.search('title=".*?"',untreatedInfo).group(0).replace('title=','').replace('"','')
这里replace方法是可以无限套娃的,但是涉及底层原理尽量不要超过128个,部分电脑可能报错。我直接把title= 和 双引号”替换成了空,这个空不是null就是字符串的空。结果就正常了
到目前为止,
我们了解了如何通过网址获得这个网页的全部HTML代码;
了解了如何通过XPath获取网页标签的值和他的属性;
了解了如何根据关键字查找并根据字符下标截取一段网页代码里我们需要的内容了解了如何写一段简单的正则表达式了;
了解了如何使用正则表达式在一大段字符串里找到我们需要的内容了解了如何改变一段文本中的部分内容
关于信息获取这里,基本上我能想到的常用的方法就是这些了。
关于正则表达式、Xpath相关知识,如果有兴趣的可以查找相关的资料进一步了解。
今天就先到这里,之后我会给大家介绍:如何通过文件的读写来判断是否出现新的需求如何把获取的数据分门别类的保存进数据库如何把获取的最新订单信息发送给我们如何把程序放进远程服务器里执行最后如何完整的把一个简单的demo变成可以支持读取不同分类中,所有新需求并及时发送提醒,且长期不中断的可以实际使用的项目的所有细节和代码。今天份的代码在最后。
第二部分的内容:
python基础入门IO文件读写操作与邮件发送
from lxml import etree
import urllib.request
import re
#获取HTML代码的方法
#传入参数url是网址字符串
#返回参数是结果字符串
def getHtmlCode(url):
#创建对象
req_one = urllib.request.Request(url)
#header信息
req_one.add_header('User-Agent', 'Mozilla/6.0')
#加入 try catch模块防止request报错导致结束程序
try:
#注意!timeout参数如果设置了,有可能出现XX秒内没有执行完毕报错
res_one = urllib.request.urlopen(req_one,timeout=1)
#内容重编码后复制给变量
htmlcode = res_one.read().decode('utf-8')
#关闭urlopen方法
res_one.close()
#把结果返回
return htmlcode
except:
print("报错IP问题无法获得")
return "error"
#通过XPath获取网页信息的方法
#传入参数url是网址字符串
#无返回参数
def getInfoByXPath(code):
#构建eTree
html = etree.HTML(code)
#通过XPath获取标题所在<a>标签的属性
name = html.xpath('/html/body/div/div/div/div/div/div/h3/a')
#通过XPath获取预算所在标签的属性
price = html.xpath('/html/body/div/div/div/div/div/div/h3/b')
print('通过XPath获取的最新的需求是:')
#一个html标签之间的文本信息使用.text获取
print(name.text.replace(' ','').replace(' ',''))
#a 标签的href属性也就是链接属性可以使用get('属性名')来获取
print(name.get('href'))
print(name.get('indus_name'))
print(price.text+'\r\n\r\n\r\n')
#通过字符串获取网页信息的方法
#传入参数url是网址字符串
#无返回参数
def getInfoByKeyWord(code):
startTarget = '<div class="task_class_list_li_box">'
#先找到列表所在的DIV层,使用index方法
taskStartIndex = code.index(startTarget)+len(startTarget)
taskEndIndex = code.index(startTarget,taskStartIndex,len(code))
untreatedInfo = code
taskUrl = re.search('https://task.epwk.com/\d*/', untreatedInfo) .group(0)
taskTitle = re.search('title=".*?"',untreatedInfo).group(0).replace('title=','').replace('"','')
print('通过字符串获取的最新的需求是:')
print(taskUrl)
print(taskTitle)
if __name__ == '__main__':
htmlCode = getHtmlCode("https://task.epwk.com/sj/?o=7")
getInfoByXPath(htmlCode)
getInfoByKeyWord(htmlCode)
不好意思,第一次发帖,刚开始是有时间限制,我就保存在word里,今天发帖发现好像富文本编辑器复制过来格式了,现在很多显示的都有问题,我一会重新编辑一下。见谅 感谢分享,慢慢研究看看 我觉得把word文档发链接出来比较好,现在格式真的有点不想看 = = 66666感谢分享技术
页:
[1]