利用正则和bs4爬取sec-wiki.com(单线程)

首先来张图

此网站的url为http://www.sec-wiki.com/news/index

如图所示,首页上是资讯列表,点击标题后会跳到详情页,然后详情页也有一个和首页相同的标题,这个标题中包含着真正的文章地址,所以我们要做的就是先获取第一页中的标题链接,根据这个链接进入到详情页面,然后从详情页获取到标题的名称,连接,和时间即可。  

但是还有一个问题,和电影天堂一样,在首页中可以看到,我们的电影或者资讯文章有很多,首页没有完全列出来,而是分页了,根据观察可以看出翻页的url很有规律:http://www.sec-wiki.com/news/index?News_page=,等号后面填写相应的页面数就可以跳转到对应页,但是这个页面不可能没有尽头,因为文章数是有限的,这件事情有两种解决方法,如下

  • 页面数一直增长,等到某一个页面我们没有获取到需要的东西时,说明已经到终点了,抛出异常
  • 找到这个最终的页面数,然后根据这个数字进行循环

我们在这用的是第二种方法,因为在页面中查看<末页>的html代码可以看到这里边写的就是最终的页面数,我们用一个正则将这个数字匹配出来,正则表达式是这样的:<li class="last"><a href="/news/index\?News_page=(.*?)所以我们只需要根据这个提取出来的数字利用for循环就可以了,主函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
if __name__=="__main__":
newsPage = "http://www.sec-wiki.com/news/index"
homeCode = getOnePage(newsPage,0)
pattern = re.compile(r'<li class="last"><a href="/news/index\?News_page=(.*?)">',re.S)
pageSum = re.findall(pattern,homeCode)
print pageSum #这就是打印出最终页的数字
for i in range(int(pageSum[0])):#i从0开始,需要+1
childCode = getOnePage('http://www.sec-wiki.com/news/index?News_page=',i+1)
infos = getUrl(childCode)
for info in infos:
print info['time']+'%-100s'%(info['title'])+'%-100s'%(info['url'])

我们知道你要爬取任何信息的必要步骤都是要将目标url的代码先获取回来,所以将这个功能封装成一个函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def getOnePage(url,index=0):
try:
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = { 'User-Agent' : user_agent }
if index==0:
pass
else:
url = url+str(index)
request = urllib2.Request(url,headers = headers)
response = urllib2.urlopen(request)
pageCode = response.read().decode('utf-8')
return pageCode
except urllib2.URLError,e:
if hasattr(e,"reason"):
print u"错误",e.reason
return None

这里getOnePage有两个参数,一个是url,另一个是页面数,如果你要获取每一个页面的代码时第二个参数是有用的,但是如果我想根据咨询列表中获得的url进入详情页时,我只需传入一个详情页的url即可,这就用到了默认参数,将这个值设定为0,然后在函数中,判断这个值是否为0,如果为0则是想获得详情页代码,直接请求传入的url即可;如果不为0,则要将url和页面数拼凑成最终的页面url,然后再请求这个url。

现在铺垫都已经做完了,接下来完成主要的事情,就是提取信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def getUrl(childCode):
try:
infoList = []
soup = bs4.BeautifulSoup(childCode)
table = soup.find('table',attrs={'class':'items table'})
trs = table.findAll('tr')
pattern = re.compile(r'<td>.*?</td><td><a href="(.*?)" target="_blank">.*?</a></td>',re.S)
for tr in trs:
infoDic = {}
urls = re.findall(pattern,str(tr))
for url in urls:
childUrl ='http://www.sec-wiki.com'+url
sonCode = getOnePage(childUrl)
childPattern=re.compile('<strong>(.*?)</strong>.*?<a href="(.*?)">(.*?)</a>',re.S)
items = re.findall(childPattern,sonCode)
for item in items:
infoDic['time'] = item[0]
infoDic['url'] = item[1]
infoDic['title'] = item[2]
infoList.append(infoDic)
return infoList
finally:
pass

我们首先需要先从页面的代码中提取出来详情页的url,根据观察页面代码可以知道,所有的详情页的url都是在标签中,所以我们先用bs4将页面中所有的标签获取出来,然后循环这个列表,利用正则从每一个标签中将详情页的url提取出来。
 
经过观察每一个详情页的url,发现详情页的url只有路径,没有域名,所以这一步还需要将详情页路径和域名拼凑起来,就有了这一句代码childUrl ='http://www.sec-wiki.com'+url

有了详情页的url后,我们只需利用getOnePage()这个函数去获取每一个详情页的代码,然后提取最终需要的信息。  

这里也是用了一个正则表达式<strong>(.*?)</strong>.*?<a href="(.*?)">(.*?)</a>
解释一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>懒惰模式,后边多一个?表示,必须跟在*或者+后边用。
> 如要匹配的字符串为:`<img src="test.jpg" width="60px" height="80px"/>`
如果用正则匹配src中内容非懒惰模式匹配src=".*"
> 匹配结果是:src="test.jpg" width="60px" height="80px"
意思是从="往后匹配,直到最后一个"匹配结束
>懒惰模式正则:
src=".*?"
结果:src="test.jpg"
因为匹配到第一个"就结束了一次匹配。不会继续向后匹配。因为他懒惰嘛。
我写出这个正则表达式其实就是从页面中将包含我要的信息的源码先拷贝到编辑器中,然后观察哪些东西在不同页面中是变的,哪些是不变的,将会改变的东西就用`.*?`代替,它的含义就是遇到`?`后面的字符马上就停下来。那为什么要给他们加上括号呢,这样`(.*?)`是设置分组,当你用`item=re.findall`后,你就可以用item[下标]来访问第几个括号中的内容,下标从0开始。于是就有了这样的代码

childPattern=re.compile(‘(.?).?(.*?)‘,re.S)
items = re.findall(childPattern,sonCode)
for item in items:
infoDic[‘time’] = item[0]
infoDic[‘url’] = item[1]
infoDic[‘title’] = item[2]
```

上面三个(.*?)分别是文章时间、文章url、文章标题。于是对应着item[0]、item[1]、item[2]。
这样我们需要的信息就提取出来了,将每一个信息的字典添加到一个列表中返回给主函数就大功告成了。

此文件的github地址:https://github.com/lowkeynic4/crawl/blob/master/secwiki/secwiki.py