在公司分配给我的爬虫任务中,具体的信息又写需要在详情页中取得,所以需要在加入待抓取链接
我们用的框架是基于java 的webmagic ,这个框架可以在启动时设定多个线程抓取,所以待抓取的多个链接可能是跑在不同线程上的,但是最后需要统计,一共抓取了多少条信息,这就需要线程同步了。
我思考了一下,觉得如果是加锁还不如一个线程跑到底,而且这个计数相对于抓取来说仅仅起统计的作用,于是我想到了学习多线程时的一个修饰符:volatile
刚才说了,线程同步,其实就是对共享对象加锁,而volatile起什么作用呢,那就是这个修饰符修饰的变量,在每次读取时,都会直接从主存中刷新,也就是即时同步。这样,我们就不用担心计数的问题了,每次i++时,都会从主存中读取i的值,不会造成计数的错误。
但是volatile使用时要注意什么呢?首先,在多线程中同步中动作应当时原子性的,而被volatile修饰的变量不是原子性的,仅仅是使用时会从主存中即时读取罢了,其安全性肯定不如锁机制,但也因此获得了较高的执行效率。在这个程序中,计数仅仅需要数字的正确性,不涉及其他复杂操作,所以适宜使用volitile
下面是源代码,其中一些为配置性代码
@Slf4j public class MulberryPageProcessor implements PageProcessor { public volatile int i=0; //http://www.mulberry.com/cn/stores-list private static final String List_Url_Regex="http://www\\.mulberry\\.com/cn/stores-list"; private Site site = Site .me() .setSleepTime(1000) .setRetryTimes(3) .setRetrySleepTime(5000) .setTimeOut(30000) .addHeader("Accept-Language", "zh-CN,zh;q=0.8,en;q=0.6") .setUserAgent( "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36"); //定义静态hashmap进行数据的存储 public static HashMap<String,String[]> list=new HashMap<String, String[]>(); public List<BrandPoiDto> brandPoiDtos = new ArrayList<BrandPoiDto>(); //对url进行正则提取 private Pattern urlPattern=Pattern.compile("(?<=href=\").*(?=\" title)"); //利用List存储要入库的数据 @Override public void process(Page page){ //判断是否为列表页 if(page.getUrl().regex(List_Url_Regex).match()) { // System.out.print(page.getHtml().toString()); //分国家爬取列表 List<Selectable> CountryList=page.getHtml().xpath("li[@class='store-country']").nodes(); String country=""; String branchname=""; //循环,将国家名称,分店名截取出来,剩下的详细信息在详情页截取 for(Selectable a:CountryList){ //截取国家名称 country=a.xpath("//span[@class='store-country__name']/text()").toString(); //截取分店列表 List<Selectable> ShopList =a.xpath("//li[@class='stores-list__item']").nodes(); //对分店列表做循环 for(Selectable b:ShopList) { //提取分店名称 branchname=b.xpath("//a[@class='stores-list__link']/text()").toString(); //提取分店链接url,因为xpath提供的正则有些东西换转码,所以用自己写的 // String BranchUrl=b.xpath("//a[@class='stores-list__link']/@href/text()").toString(); String BranchUrl=""; Matcher urlMatcher=urlPattern.matcher(b.toString()); if(urlMatcher.find()) { BranchUrl=urlMatcher.group(); } //将分店的url作为map中的key,将分店名和所在国家存储进去 if(BranchUrl!=null) { //将国家和分店信息存储进value里,将分店详情链接作为key String [] value={country,branchname}; list.put(BranchUrl,value); // System.out.println("url["+i+"] =\""+BranchUrl+"\";"); // 将详情界面添加到爬取的界面当中 page.addTargetRequest(BranchUrl); //对分店名,url初始化 branchname=""; BranchUrl=""; } else { //此时应当记录日志 System.out.print("详情页url没获取到"); } } //将国家初始化 country=""; } } //若为详情页 else { try{ String BranchName=""; //使用线程安全的Buffer StringBuffer Opentime=new StringBuffer(); String Phone=""; String CrawUrl=page.getUrl().toString(); StringBuilder Address=new StringBuilder(); String City=""; String Country=""; String Lat=""; double latitude=0.0;//截取出来的为字符串,必须转化为数字进行存储 String Lng=""; double longitude=0.0; String CrawlUrl=page.getUrl().toString(); String Region=""; int CategoryId=658; //经纬度的截取,并转化为double Lat=page.getHtml().xpath("div[@id='storeGeoDetails']//@data-store-lat").toString(); Lng=page.getHtml().xpath("div[@id='storeGeoDetails']//@data-store-long").toString(); latitude=Double.parseDouble(Lat); longitude=Double.parseDouble(Lng); //电话号码的获取 Phone=page.getHtml().xpath("li[@class='tel']/text()").toString(); //城市名称的截取 //先将li标签循环存储 List<Selectable> LiList =page.getHtml().xpath("ul[@class='contact']/li/text()").nodes(); //城市名在倒数第4个li标签中 try{ for(int i=0;i<LiList.size()-3;i++) { if(i<LiList.size()-4) { //已测试成功 Address.append(LiList.get(i).toString()); } //在之前进行字符串的拼接,拼接结果为地址 选用StringBuild if(i==LiList.size()-4) City=LiList.get(i).toString(); } } catch(Exception e) { } //营业时间的截取,需要去除标签 // Opentime=page.getHtml().xpath("table[@class='store-times']//text()").toString(); List<Selectable> OpenTimeList=page.getHtml().xpath("table[@class='store-times']/tbody/tr/td/text()").nodes(); //循环输出 for(Selectable a:OpenTimeList) { Opentime=Opentime.append(a.toString()); } //从hashmap中取出国家与分店信息,key为value值 String[] contain =new String[2]; contain=list.get(page.getUrl().toString()); Country=contain[0]; BranchName=contain[1]; //获取工作完成,存入dto中 BrandPoiDto dto = new BrandPoiDto(); // dto.setShopId(ShopId); dto.setBranchName(BranchName); dto.setAddress(Address.toString()); dto.setPhone(Phone); dto.setOpenTime(Opentime.toString()); dto.setCrawlUrl(CrawlUrl); dto.setCountry(Country); dto.setCity(City); dto.setRegion(Region); dto.setLat(latitude); dto.setLng(longitude); dto.setSourceName("Mulberry"); i++; System.out.println(i); brandPoiDtos.add(dto); page.putField("brandPoiList", brandPoiDtos); brandPoiDtos.clear(); } catch(Exception e) { log.error("method:TripAdvisorPoiReviewPageProcessor.process,parse url[{}] exception[{}]", page.getUrl(), e); // System.out.println(page.getUrl()); } }